diff options
| author | Didier Roche <didier.roche@canonical.com> | 2012-03-12 10:18:48 +0100 |
|---|---|---|
| committer | Didier Roche <didier.roche@canonical.com> | 2012-03-12 10:18:48 +0100 |
| commit | a28c11ceea6bfa1c2d4860e7e6b77b627e267a2b (patch) | |
| tree | f0b4f9a9c32362eec4013dbcce727a1722e2e22c | |
| parent | df9655d659ed8413c0e42836f344a34f9f3f18a5 (diff) | |
| parent | eae878cd629cdbb4d68d32d9325b3934ded4178b (diff) | |
New upstream release.
(bzr r55.3.600)
142 files changed, 6942 insertions, 1957 deletions
@@ -1,5 +1,6 @@ 3v1n0 <mail@3v1n0.net>, Marco Trevisan (Treviño) <mail@3v1n0.net> agateau <aurelien.gateau@canonical.com> + alanbell@ubuntu.com Alejandro Piñeiro <apinheiro@igalia.com> Alexandros Frantzis <alexandros.frantzis@linaro.org>, Marc Ordinas i Llopis <marc.ordinasillopis@linaro.org>, Jay Taoko <jay.taoko@canonical.com> Alex Launi <alex.launi@canonical.com> @@ -10,16 +11,20 @@ Andrey Logvinov <Andrey.Logvinov.81@gmail.com Aurélien Gâteau <aurelien.gateau@canonical.com> Bilal Akhtar <bilalakhtar@ubuntu.com> + Brandon Schaefer <brandontschaefer@gmail.com Brandon Schaefer <brandontschaefer@gmail.com> + Brandon Schaefer <brandontschaefer@gmail.com, Jay Taoko <jay.taoko@canonical.com> Brandon Schaefer <schbra02@evergreen.edu> Chase Douglas <chase.douglas@canonical.com> Chris Coulson <chris.coulson@canonical.com> Christopher James Halse Rogers <raof@ubuntu.com> + Daniel d'Andrada <daniel.dandrada@canonical.com> Daniel van Vugt <vanvugt@gmail.com> David Barth <david.barth@canonical.com> David Gomes <davidrafagomes@gmail.com> David Planella <david.planella@ubuntu.com> Didier Roche <didier.roche@canonical.com> + Gabor Kelemen <kelemeng@ubuntu.com> Gordon Allott <gord.allott@canonical.com> Henri De Veene <henri.deveene@gmail.com> Jani Monoses <jani.monoses@canonical.com> @@ -29,9 +34,12 @@ Jason Smith <jason.smith@canonical.com>, Andrea Cimitan <andrea.cimitan@canonical.com> Jason Smith <jason.smith@canonical.com>, Marco Trevisan (Treviño) <mail@3v1n0.net>, Thomi Richards <thomi.richards@canonical.com> Jason Smith <jason.smith@canonical.com>, smspillaz <sam.spilsbury@canonical.com> + Jason Smith <jason.smith@canonical.com>, Tim Penhey <tim.penhey@canonical.com> jassmith@gmail.com Jay Ó Broin <ismise@lavabit.com> Jay Taoko <jay.taoko@canonical.com> + Jeremy Bicha <jbicha@ubuntu.com> + Lars Uebernickel <lars.uebernickel@canonical.com> Loïc Molinari <loic.molinari@canonical.com> Luke Yelavich <luke.yelavich@canonical.com> Marco Biscaro <marcobiscaro2112@gmail.com> @@ -41,6 +49,7 @@ Marco Trevisan (Treviño) <mail@3v1n0.net> Marco Trevisan (Treviño) <mail@3v1n0.net>, 3v1n0 <mail@3v1n0.net> Marco Trevisan (Treviño) <mail@3v1n0.net>, Andrea Cimitan <andrea.cimitan@canonical.com> + Marco Trevisan (Treviño) <mail@3v1n0.net>, Thomi Richards <thomi.richards@canonical.com> Marius Gedminas <marius@gedmin.as> Martin Albisetti <argentina@gmail.com> Michael Terry <michael.terry@canonical.com> @@ -53,6 +62,7 @@ Nico van der Walt <nico@kimburu.co.za> Oliver Sauder <os@esite.ch> Omer Akram <om26er@ubuntu.com> + Paul Sladen <sladen@canonical.com> Rafał Cieślak <rafalcieslak256@gmail.com> Ricardo Mendoza <ricardo.mendoza@canonical.com> Robert Carr <racarr@canonical.com> @@ -73,9 +83,12 @@ Thomi Richards <thomir@gmail.com>, Thomi Richards <thomi.richards@canonical.com> Thomi Richards <thomi.richards@canonical.com> Thomi Richards <thomi.richards@canonical.com>, Jason Smith <jason.smith@canonical.com> + Thomi Richards <thomi.richards@canonical.com>, Ted Gould <ted@gould.cx> Thomi Richards <thomi.richards@canonical.com>, Thomi Richards <thomir@gmail.com> Tim Penhey <tim.penhey@canonical.com> Tim Penhey <tim.penhey@canonical.com>, Gordon Allott <gord.allott@canonical.com> + Tim Penhey <tim.penhey@canonical.com>, Jason Smith <jason.smith@canonical.com> + Tim Penhey <tim.penhey@canonical.com>, Jay Taoko <jay.taoko@canonical.com> Ubuntu <ubuntu@netbook> Unity Merger <unity.merger@gmail.com> Victor Eduardo <victormartinez79@gmail.com> diff --git a/CMakeLists.txt b/CMakeLists.txt index 11318942b..7b1502271 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -8,7 +8,7 @@ include (cmake/Documentation.cmake) # set (PROJECT_NAME "unity") set (UNITY_MAJOR 5) -set (UNITY_MINOR 4) +set (UNITY_MINOR 6) set (UNITY_MICRO 0) set (UNITY_VERSION "${UNITY_MAJOR}.${UNITY_MINOR}.${UNITY_MICRO}") set (UNITY_API_VERSION "5.0") @@ -113,7 +113,7 @@ if (BOOT_LOGGER) SET (BOOT_LOGGER_FLAG "-DENABLE_LOGGER") endif (BOOT_LOGGER) -SET (MAINTAINER_CFLAGS "-Wall -Wcast-align -Wno-uninitialized -Wempty-body -Wformat-security -Winit-self -Warray-bounds") +SET (MAINTAINER_CFLAGS "-Werror -Wall -Wcast-align -Wno-uninitialized -Wempty-body -Wformat-security -Winit-self -Warray-bounds") option (DISABLE_MAINTAINER_CFLAGS "Disable maintainer CFlags" OFF) if (DISABLE_MAINTAINER_CFLAGS) SET (MAINTAINER_CFLAGS "") @@ -132,6 +132,13 @@ add_subdirectory(plugins/unity-mt-grab-handles) # subdirs add_subdirectory(doc) + +find_path(GTEST_INCLUDE_DIR gtest/gtest.h) +if (GTEST_INCLUDE_DIR) + #FIXME - hardcoded is bad! + add_subdirectory(/usr/src/gtest gtest) +endif(GTEST_INCLUDE_DIR) + add_subdirectory(services) add_subdirectory(tests) add_subdirectory(tools) @@ -1,3 +1,1960 @@ +2012-03-12 Thomi Richards <thomi.richards@canonical.com> + + Fixed the CommandLensSearchTests.test_run_before_refresh test so it passes, and refactored the bamf emulator.. Fixes: . Approved by Tim Penhey. + +2012-03-12 Thomi Richards <thomi.richards@canonical.com> + + Cleaned up command lens, which required removing large quantities of code from the bamf emulator. + +2012-03-12 Thomi Richards <thomi.richards@canonical.com> + + Updated show desktop autopilot test to use new launcher API.. Fixes: . Approved by Tim Penhey. + +2012-03-12 Thomi Richards <thomi.richards@canonical.com> + + Fixed show_desktop test. + +2012-03-11 Thomi Richards <thomi.richards@canonical.com> + + Poll for visibility to avoid system differences in tests.. Fixes: . Approved by Tim Penhey. + +2012-03-12 Thomi Richards <thomi.richards@canonical.com> + + Dash emulator ensure_hidden and ensure_visible now poll the dash visibility state to make sure that the dash is visible before they return. After 10 seconds an exception is raised. + +2012-03-11 Thomi Richards <thomi.richards@canonical.com> + + The problem: + + The Autopilot test case class didn't log anything when calling set_unity_option, which made it hard to diagnose certain failing autopilot tests. + + The solution: + + The log message is now produced both when a compiz option is set, and when it is unset (as part of cleanup). + + Tests: + + Log messages from set_unity_option and set_compiz_option will appear in jenkins test logs. + + UNBLOCK. Fixes: . Approved by Tim Penhey. + +2012-03-12 Thomi Richards <thomi.richards@canonical.com> + + Removed debugging code. + +2012-03-12 Thomi Richards <thomi.richards@canonical.com> + + Fixed an issue with the autopilot logging infrastructure which meant we were missing some important log messages in certain AP tests. + +2012-03-11 Thomi Richards <thomi.richards@canonical.com> + + Fix the DashKeyboardFocusTests.test_filterbar_expansion_leaves_kb_focus autopilot test.. Fixes: . Approved by Tim Penhey. + +2012-03-12 Thomi Richards <thomi.richards@canonical.com> + + FilterBar class can now get the correct searchbar. + +2012-03-11 Thomi Richards <thomi.richards@canonical.com> + + Fixed a timing issue with the dash reveal behavior.. Fixes: . Approved by Tim Penhey. + +2012-03-12 Thomi Richards <thomi.richards@canonical.com> + + Fixed a timing issue with the dash reveal behavior. This was causing the dash to appear and dissapear quickly when hitting Super+a (or any other lens shortcut). Also fixed a memory leak in the DashController. + +2012-03-11 Thomi Richards <thomi.richards@canonical.com> + + Only attach video capture log to failing tests if the video capture process returned non-0. Video capture logs aren't useful unless the capture process failed for some reason. + + UNBLOCK. Fixes: . Approved by Tim Penhey. + +2012-03-12 Thomi Richards <thomi.richards@canonical.com> + + Only attach video capture log if the capture process returned non-0 exit code. Capture logs aren't that useful anyway. + +2012-03-11 Thomi Richards <thomi.richards@canonical.com> + + This branch slows down the show desktop tests. + + As far as I can see, they're failing on the jenkins instance due to timing issues. This branch should help resolve that (or at least eliminate that possibility). + + UNBLOCK. Fixes: . Approved by Tim Penhey. + +2012-03-12 Thomi Richards <thomi.richards@canonical.com> + + Wait a bit longer after initiating show desktop mode. This should help these tests pass on the jenkins instance. + +2012-03-09 Gord Allott <gord.allott@canonical.com> + + Fixes the hud not returning focus on return + + UNBLOCK. Fixes: https://bugs.launchpad.net/bugs/934061. Approved by . + +2012-03-09 Gord Allott <gord.allott@canonical.com> + + fixes issue with focus on return not being returned to the application + +2012-03-09 Thomi Richards <thomi.richards@canonical.com> + + This branch adds autopilot tests to verify the fix for bug lp:942042. + + UNBLOCK. Fixes: https://bugs.launchpad.net/bugs/942042. Approved by Tim Penhey. + +2012-03-07 Thomi Richards <thomi.richards@canonical.com> + + Cleanups. + +2012-03-07 Thomi Richards <thomi.richards@canonical.com> + + added quicklist test file. + +2012-03-07 Thomi Richards <thomi.richards@canonical.com> + + added python-xdg to the dependancy list for autopilot. + +2012-03-07 Thomi Richards <thomi.richards@canonical.com> + + Merged branch with bugfix, test passes. + +2012-02-28 Ted Gould <ted@gould.cx> + + Removing a free'ing of the keyfile that was removed + +2012-02-27 Ted Gould <ted@gould.cx> + + Drop unused variables + +2012-02-27 Ted Gould <ted@gould.cx> + + Attaching Bug + +2012-02-27 Ted Gould <ted@gould.cx> + + Libindicator handles the case where we don't find shortcuts without warning now, don't need this if statement. This brings Unity up to compliance with the name changes in the desktop spec + +2012-03-07 Thomi Richards <thomi.richards@canonical.com> + + Have a failing test. + +2012-03-09 Gord Allott <gord.allott@canonical.com> + + fixes issue with hud not returning focus correctly, hack was bad. + + UNBLOCK. Fixes: https://bugs.launchpad.net/bugs/934061. Approved by Gord Allott, Mirco Müller. + +2012-03-05 Gord Allott <gord.allott@canonical.com> + + resolved conflict + +2012-03-05 Gord Allott <gord.allott@canonical.com> + + remove debugs + +2012-03-02 Gord Allott <gord.allott@canonical.com> + + fixed up the bamf wrapper to use desktop files for matching rather than application names, should work on non english locales now + +2012-03-02 Gord Allott <gord.allott@canonical.com> + + merged trunk and added ap tests + +2012-02-23 Gord Allott <gord.allott@canonical.com> + + remove stupid po changes i hate that + +2012-02-23 Gord Allott <gord.allott@canonical.com> + + fixes the hud not giving focus back if you open close the hud fast, no tests + +2012-03-09 Gord Allott <gord.allott@canonical.com> + + UNBLOCK + + dummy merge to bump abi. Fixes: . Approved by Didier Roche, Gord Allott. + +2012-03-08 Gord Allott <gord.allott@canonical.com> + + corrected HACKING file + +2012-03-08 Jeremy Bicha <jbicha@ubuntu.com> + + UNBLOCK: set title case for Lock to Launcher/Unlock from Launcher. Fixes: https://bugs.launchpad.net/bugs/949636. Approved by Didier Roche. + +2012-03-07 Jeremy Bicha <jbicha@ubuntu.com> + + Use title case for "Lock to Launcher"/"Unlock from Launcher" (LP: #949636) + +2012-03-08 Gord Allott <gord.allott@canonical.com> + + Includes the google test library in our build system, could use some slimming down after some investigation + + UNBLOCK. Fixes: . Approved by . + +2012-03-08 Gord Allott <gord.allott@canonical.com> + + more graceful handling of no gtest + +2012-03-08 Gord Allott <gord.allott@canonical.com> + + builds cmake locally using distro supplied cmake - hardcoded to /usr/src/gtest right now + +2012-03-08 Gord Allott <gord.allott@canonical.com> + + adds the entire gtest library to our build system as per googles preference + +2012-03-07 Thomi Richards <thomi.richards@canonical.com> + + Autopilot now logs when it sets a compiz plugin option as part of a test run. Fixed a bug in a log message inside the Mouse class. + + UNBLOCK. Fixes: . Approved by Alex Launi. + +2012-03-08 Thomi Richards <thomi.richards@canonical.com> + + Fixed a bug in the Mouse class log message. + +2012-03-08 Thomi Richards <thomi.richards@canonical.com> + + Added logging to code that alters unity settings. + +2012-03-07 Jay Taoko <jay.taoko@canonical.com> + + * Revert composition character support in Unity. It introduced a regression in Unity. + + UNBLOCK. Fixes: . Approved by Alex Launi. + +2012-03-07 Jay Taoko <jay.taoko@canonical.com> + + * Revert composition character support in Unity. It introduced a regression in Unity. + +2012-03-06 Marco Trevisan (Treviño) <mail@3v1n0.net> + + UnityScreen: don't ungrab the keyboard on Alt press if switcher is active + + When the Alt+Tab switcher is active is the keyboard must not be ungrabbed or the Alt+Tab cancel action won't work (i.e. escaping from switcher by Escape key). + This fixes a regression introduced recently. I've also included AP tests to check if the cancel action works. + + UNBLOCK. Fixes: https://bugs.launchpad.net/bugs/948227. Approved by Tim Penhey, Thomi Richards. + +2012-03-06 Marco Trevisan (Treviño) <mail@3v1n0.net> + + autopilot, test_switcher.py: added tests for switcher escaping, and lazy invocation + +2012-03-06 Marco Trevisan (Treviño) <mail@3v1n0.net> + + UnityScreen: don't ungrab the keyboard on Alt press if switcher is active + + When the Alt+Tab switcher is active is the keyboard must not be + ungrabbed or the Alt+Tab cancel action won't work (i.e. escaping + from switcher by Escape key). + +2012-03-06 Thomi Richards <thomi.richards@canonical.com> + + Adds an option to the autopilot script that allows users to pick which folder to store videos of failed tests. This is needed by the jenkins infrastructure. + + UNBLOCK. Fixes: . Approved by Alex Launi. + +2012-03-07 Thomi Richards <thomi.richards@canonical.com> + + Adds an option to the autopilot script so we can pick where to store videos of failed tests. + +2012-03-06 Thomi Richards <thomi.richards@canonical.com> + + Add the ability to video capture failing autopilot tests. This feature is enabled with the '-r' switch to the 'autopilot run' command. + + Videos are stored in /tmp/autopilot. This feature is intended for use on the jenkins slave machines. + + UNBLOCK. Fixes: . Approved by Marco Trevisan (Treviño), Tim Penhey. + +2012-03-07 Thomi Richards <thomi.richards@canonical.com> + + Merged trunk, fixed conflicts. + +2012-03-06 Thomi Richards <thomi.richards@canonical.com> + + Whitespace fixes. + +2012-03-06 Thomi Richards <thomi.richards@canonical.com> + + Prevent passing tests from encoding the video file, so we don't suffer the encoding overhead. Removed the dummy test I was using to test this feature. + +2012-03-06 Thomi Richards <thomi.richards@canonical.com> + + Recording is disabled if the recording app is not present. + +2012-03-06 Thomi Richards <thomi.richards@canonical.com> + + Video capture works. + +2012-03-06 Jay Taoko <jay.taoko@canonical.com> + + UNBLOCK + + * Use Gtk im for composition characters. + + Fix bug #944674. Fixes: https://bugs.launchpad.net/bugs/944674. Approved by Neil J. Patel. + +2012-03-06 Jay Taoko <jay.taoko@canonical.com> + + * Simulate 'ô' in a test. + +2012-03-05 Jay Taoko <jay.taoko@canonical.com> + + * Merged with trunk. + +2012-03-05 Jay Taoko <jay.taoko@canonical.com> + + * Fixed errors in tests + +2012-03-05 Jay Taoko <jay.taoko@canonical.com> + + * Adding Autopilot test for composition characters (â,ô,ĩ,î,ã) typing in the dash. + +2012-03-05 Jay Taoko <jay.taoko@canonical.com> + + * Fix + +2012-03-05 Jay Taoko <jay.taoko@canonical.com> + + * Cleanup + +2012-03-05 Jay Taoko <jay.taoko@canonical.com> + + * Fix: removed bad conditional. + +2012-03-02 Jay Taoko <jay.taoko@canonical.com> + + * Use Gtk im for composition characters. + +2012-03-02 Jay Taoko <jay.taoko@canonical.com> + + * Adding support for dead keys. + +2012-03-06 Gord Allott <gord.allott@canonical.com> + + UNBLOCK resolves a silly glib issue, glib is C not C++ ;). Fixes: . Approved by Michał Sawicz. + +2012-03-06 Gord Allott <gord.allott@canonical.com> + + resolves a 2d build issue + +2012-03-04 Thomi Richards <thomi.richards@canonical.com> + + Refactor the autopilot launcher emulator to use the new UnityIntrospectableObject class. Launcher tests will now run once per configured monitor.. Fixes: . Approved by Tim Penhey. + +2012-03-05 Thomi Richards <thomi.richards@canonical.com> + + Fixed typo, made show desktop tests even nicer to read. + +2012-03-05 Thomi Richards <thomi.richards@canonical.com> + + Added a TODO comment before switcher emulator prompting me to refactor it in another branch. + +2012-03-05 Thomi Richards <thomi.richards@canonical.com> + + Fixed filtering in get_children_by_type. + +2012-03-05 Thomi Richards <thomi.richards@canonical.com> + + Imports should be system first, then local, and in alphabetical order. + +2012-03-05 Thomi Richards <thomi.richards@canonical.com> + + Removed debug logging that was flooding the test logs, fixed showdesktop tests. + +2012-03-05 Thomi Richards <thomi.richards@canonical.com> + + Fixed a breakage in the hud tests, and fixed a bug in the UnityIntrospectableObject class, where children would be filtered if no filter was applied. + +2012-03-05 Thomi Richards <thomi.richards@canonical.com> + + Launcher emulator is now split into separate classes: LauncherController, LauncherModel, and Launcher. Updated tests. Launcher tests are now scenario'd depending on the number of monitors configured on the machine. All new-style emulators automatically update their state when someone requests an attribute from unity. + +2012-03-05 Thomi Richards <thomi.richards@canonical.com> + + Renamed Launcher to LauncherHelper, added LauncherController and Launcher classes. Tests still pass. + +2012-03-04 Thomi Richards <thomi.richards@canonical.com> + + Modifies the tools/autopilot script to print the total number of tests found. + + UNBLOCK. Fixes: . Approved by Tim Penhey. + +2012-03-03 Thomi Richards <thomi.richards@canonical.com> + + autopilot script prints total number of tests after listing them. + +2012-03-04 Gord Allott <gord.allott@canonical.com> + + Check pixbuf before dereferencing.. Fixes: https://bugs.launchpad.net/bugs/937421. Approved by Tim Penhey. + +2012-02-28 Gord Allott <gord.allott@canonical.com> + + add safety check on gdk pixbuf + +2012-03-04 smspillaz <sam.spilsbury@canonical.com> + + Fix the Alt+F1 or Alt+F2 sending a ";3P" or ";3Q" to the active windows issue, and also fixes some alt holding showing menus. Compiz-core fix needed for complete fix.. Fixes: https://bugs.launchpad.net/bugs/943194, https://bugs.launchpad.net/bugs/943239, https://bugs.launchpad.net/bugs/943456. Approved by Tim Penhey, Sam Spilsbury, Daniel van Vugt. + +2012-03-02 smspillaz <sam.spilsbury@canonical.com> + + Added a small work-a-round for bug 943194 + +2012-03-02 smspillaz <sam.spilsbury@canonical.com> + + Always return true whenever an action was handled + +2012-03-04 Jason Smith <jason.smith@canonical.com> + + Don't show the switcher when alt+right is pressed. Fixes: https://bugs.launchpad.net/bugs/943902. Approved by Tim Penhey. + +2012-03-04 Jason Smith <jason.smith@canonical.com> + + add test + +2012-03-04 Jason Smith <jason.smith@canonical.com> + + Fix alt-arrow causing switcher to pop up + +2012-03-02 Daniel d'Andrada <daniel.dandrada@canonical.com> + + Fixes lp:942625 + + GestureEngine::FindCompWindow() would enter in an infinite loop if the window passed to it is the root window since its break condition (parent == root) would never be reached as parent would be zero. + + Also includes some other safeguards around the same issue. + + UNBLOCK. Fixes: https://bugs.launchpad.net/bugs/942625. Approved by Marco Trevisan (Treviño). + +2012-03-01 Daniel d'Andrada <daniel.dandrada@canonical.com> + + Fixes infinite loop in GestureEngine. lp:942625 + + GestureEngine::FindCompWindow() would enter in an infinite loop + if the window passed to it is the root window since its break condition + (parent == root) would never be reached as parent would be zero. + +2012-03-02 Tim Penhey <tim.penhey@canonical.com> + + * Fixes the key navigation in the hud. Implemented an "ok" behaviour when navigationg with the key. This will have to be refined with input from design. + + * This branch relies on this nux branch: https://code.launchpad.net/~unity-team/nux/nux.hud-fixes/+merge/95399 + + UNBLOCK + . Fixes: . Approved by Tim Penhey, Gord Allott. + +2012-03-02 Tim Penhey <tim.penhey@canonical.com> + + Merge trunk. + +2012-03-02 Tim Penhey <tim.penhey@canonical.com> + + Make the base autopilot test case use the glib runner, and add more hud tests. + +2012-03-02 Tim Penhey <tim.penhey@canonical.com> + + Expose the HudView through the controller. + +2012-03-02 Tim Penhey <tim.penhey@canonical.com> + + Remember the selected button, and expose over debug interface. + +2012-03-02 Tim Penhey <tim.penhey@canonical.com> + + Initialize the view member, and add the view as an introspectable child. + +2012-03-02 Tim Penhey <tim.penhey@canonical.com> + + Add an unsigned type to the variant wrapper. + +2012-03-01 Jay Taoko <jay.taoko@canonical.com> + + * Fix: properly updated the app icons when navigating through the Hud buttons + * Fix left and right navigation keys in the text entry + * Cleanup + +2012-03-01 Jay Taoko <jay.taoko@canonical.com> + + * Cleanup and comments. + +2012-03-01 Jay Taoko <jay.taoko@canonical.com> + + * Fixes the key navigation in the hud. Implemented an "ok" behaviour when navigationg with the key. This will have to be refined with input from design. + +2012-03-02 Thomi Richards <thomi.richards@canonical.com> + + Two changes to the key the Keyboard emulator handles multi-key combos: We insert a delay between pressing and releasing individual keys, and always release keys in the reverse order they were pressed. + + UNBLOCK. Fixes: . Approved by Marco Trevisan (Treviño), Alex Launi. + +2012-03-02 Thomi Richards <thomi.richards@canonical.com> + + Keyboard emulator now inserts a delay between each key in a multi-key press or release action. + +2012-02-29 Marco Trevisan (Treviño) <mail@3v1n0.net> + + The launcher icons can be shown during the Super Tab Launcher switcher. + + For both the shortcut hints and the icons shortcut overlays we use the same policy: + - If they were shown before starting the Super+Tab session, we still show them + - If they were not shown before starting the Super+Tab session, they won't be ever shown + + Plus, if we're pressing a valid unity shortcut key when the Launcher Switcher is active, we should terminate the switcher without doing anything. + + UNBLOCK. Fixes: https://bugs.launchpad.net/bugs/943372, https://bugs.launchpad.net/bugs/943377. Approved by Thomi Richards. + +2012-03-01 Marco Trevisan (Treviño) <mail@3v1n0.net> + + autopilot, more test_launcher.py fixes + +2012-03-01 Marco Trevisan (Treviño) <mail@3v1n0.net> + + Merging with lp:~thomir/unity/fix-ap-test-stability + +2012-03-01 Marco Trevisan (Treviño) <mail@3v1n0.net> + + test_launcher.py: fixed some errors in tests + +2012-02-29 Marco Trevisan (Treviño) <mail@3v1n0.net> + + Merging with lp:~thomir/unity/super+tab-shortcut-fixes-bettertests + +2012-03-01 Thomi Richards <thomi.richards@canonical.com> + + Split apart launcher tests into many smaller tests. + +2012-02-29 Marco Trevisan (Treviño) <mail@3v1n0.net> + + autopilot, test_launcher.py: Added the switcher starts on zero test + +2012-02-29 Marco Trevisan (Treviño) <mail@3v1n0.net> + + autopilot, test_launcher.py: Fixing the docstrings + + Plus use self.keyboard. + +2012-02-29 Marco Trevisan (Treviño) <mail@3v1n0.net> + + Merging with trunk. + +2012-02-29 Marco Trevisan (Treviño) <mail@3v1n0.net> + + manual-tests: SuperTab, removed the tests now covered by autopilot + +2012-02-29 Marco Trevisan (Treviño) <mail@3v1n0.net> + + autopilot, test_launcher: added Super+Tab cycling tests + +2012-02-29 Marco Trevisan (Treviño) <mail@3v1n0.net> + + autopilot, test_launcher: added tests for bug #943372 and bug #943372 + + We both check the interaction with the launcher icon shortcuts, both + from a visual and logical point of view. + +2012-02-29 Marco Trevisan (Treviño) <mail@3v1n0.net> + + autopilot, launcher emulator: add getters for shortcuts status and keyboard target launcher + +2012-02-29 Marco Trevisan (Treviño) <mail@3v1n0.net> + + LauncherController: add introspection to get the keyboard target launcher + +2012-02-29 Marco Trevisan (Treviño) <mail@3v1n0.net> + + Launcher: add introspect to check if the shortcuts overlays are showing + +2012-02-29 Marco Trevisan (Treviño) <mail@3v1n0.net> + + Merging with trunk + +2012-02-02 Marco Trevisan (Treviño) <mail@3v1n0.net> + + No need to terminate the Key navigation releasing super. + +2012-02-02 Marco Trevisan (Treviño) <mail@3v1n0.net> + + manual-tests: SuperTab, one more test to check the interaction with the unity shortcuts + +2012-02-02 Marco Trevisan (Treviño) <mail@3v1n0.net> + + manual-test: added missing manual test for SuperTab and launcher icons shortcuts interaction + +2012-02-02 Marco Trevisan (Treviño) <mail@3v1n0.net> + + UnityScreen: if a valid shortcut key is pressed and the keynavigation is active, terminate it + +2012-02-02 Marco Trevisan (Treviño) <mail@3v1n0.net> + + LauncherController: don't hide the icon shortcuts on KeyNavigation + + They should be shown if they've been already there when starting the navigation. + +2012-02-29 Thomi Richards <thomi.richards@canonical.com> + + Two tests are now skipped. These need to be rewritten, but this is a non-trivial test to write. The current tests try and do something that's not supported by unity. + + UNBLOCK. Fixes: . Approved by Tim Penhey. + +2012-03-01 Thomi Richards <thomi.richards@canonical.com> + + Skipping tests that modify _NET_WM_STATE property after a window has been mapped, since unity doesn't support that. + +2012-02-29 Thomi Richards <thomi.richards@canonical.com> + + Fixed several stability issues with autopilot tests. + + Specifically: + + * hud tests now make sure the hud is hidden after each test. + * Keyboard and mouse cleanup happens after test tearDown methods are called, so tests can do things like hide the dash and the keyboard cleanup will still work as expected. + * Keyboard cleanup now removes pressed keys from the global list after releasing them (oops!) + * Mouse cleanup now releases pressed mouse buttons as well as moving mouse to a safe position. + * Keyboard and mouse release methods will log a warning when asked to release a key that wasn't pressed, rather than crashing. This shouldn't normally happen, but crashing is never the right thing to do. + * Revealing the dash, or any lens will now clear the old search text by default. + + UNBLOCK. Fixes: . Approved by Marco Trevisan (Treviño). + +2012-03-01 Thomi Richards <thomi.richards@canonical.com> + + Fixed typo. + +2012-03-01 Thomi Richards <thomi.richards@canonical.com> + + Remove mouse and keyboard cleanup methods from LoggedTestCase tearDown method. + +2012-03-01 Thomi Richards <thomi.richards@canonical.com> + + Mouse and keyboard clenup must happen after test tearDown, so add them as a cleanup method. + +2012-03-01 Thomi Richards <thomi.richards@canonical.com> + + Launcher can have 0 active icons. + +2012-03-01 Thomi Richards <thomi.richards@canonical.com> + + Fixed type. + +2012-03-01 Thomi Richards <thomi.richards@canonical.com> + + Log when we start an application. + +2012-03-01 Thomi Richards <thomi.richards@canonical.com> + + Only count visible launcher icons as potentially active. + +2012-03-01 Thomi Richards <thomi.richards@canonical.com> + + Hud tests now ensure hud is hidden after each test. + +2012-03-01 Thomi Richards <thomi.richards@canonical.com> + + Keyboard and Mouse emulators now warn if asked to generate a release even for a key/button that was not pressed, rather than throwing an exception. + +2012-03-01 Thomi Richards <thomi.richards@canonical.com> + + By default, revealing the dash (or any lens) clears the last search string. + +2012-03-01 Thomi Richards <thomi.richards@canonical.com> + + Keyboard cleanup method only releases keys once. Mouse cleanup method now releases buttons that were previously pushed. Keyboard and mouse cleanup happens AFTER test cleanup. + +2012-02-29 Tim Penhey <tim.penhey@canonical.com> + + Uses the new modifier-tap ability in compiz to only show the HUD if someone hasn't used another key with alt (or the associated key).. Fixes: https://bugs.launchpad.net/bugs/923410. Approved by Thomi Richards, Mirco Müller. + +2012-02-29 Tim Penhey <tim.penhey@canonical.com> + + Oops, an extra } + +2012-02-29 Tim Penhey <tim.penhey@canonical.com> + + More autopilot tests for the hud. + +2012-02-29 Tim Penhey <tim.penhey@canonical.com> + + Merge trunk + +2012-02-29 Tim Penhey <tim.penhey@canonical.com> + + A few more tests. + +2012-02-29 Tim Penhey <tim.penhey@canonical.com> + + Make the dash only show on tap, and move the 250 into a constant. + +2012-02-29 Tim Penhey <tim.penhey@canonical.com> + + Bring back the timer. + +2012-02-29 Tim Penhey <tim.penhey@canonical.com> + + Merge trunk + +2012-02-16 Tim Penhey <tim.penhey@canonical.com> + + Use the new tap behaviour in compiz to only respond to taps to show the hud. + +2012-02-29 Thomi Richards <thomi.richards@canonical.com> + + Fix the autopilot setup.py script, so jenkins will run again. + + The run_autopilot script is no longer managed by python distutils. + + UNBLOCK. Fixes: . Approved by . + +2012-02-29 Thomi Richards <thomi.richards@canonical.com> + + remove scripts from setup.py since we don't need to install that anymore. + +2012-02-28 Thomi Richards <thomi.richards@canonical.com> + + Fixed a bug in the new autopilot keybindings API which would fail to hold down single-key keybindings.. Fixes: . Approved by Marco Trevisan (Treviño). + +2012-02-29 Thomi Richards <thomi.richards@canonical.com> + + Fixed bug where single-key keybindings couldn't be held with the new keybindings system. + +2012-02-28 Jason Smith <jason.smith@canonical.com> + + Fix the details mode in alt-tab over multiple workspaces and monitors.. Fixes: https://bugs.launchpad.net/bugs/925484, https://bugs.launchpad.net/bugs/933406. Approved by Jason Smith, Thomi Richards. + +2012-02-29 Tim Penhey <tim.penhey@canonical.com> + + Clean up tests. + +2012-02-29 Tim Penhey <tim.penhey@canonical.com> + + Add test for bug: 933406 + +2012-02-29 Tim Penhey <tim.penhey@canonical.com> + + Merge trunk. + +2012-02-22 Jason Smith <jason.smith@canonical.com> + + merge fix-alt-tab-progression branch + +2012-02-22 Jason Smith <jason.smith@canonical.com> + + dont leak implementation details in switcher controller from switcher model + +2012-02-22 Jason Smith <jason.smith@canonical.com> + + merge workspaces fix + +2012-02-28 Thomi Richards <thomi.richards@canonical.com> + + run_autopilot script now warns sensibly about missing dependancies, allows users to list all tests, and run specific test(s) as well as run the entire suite. Script is now in /tools/autopilot + + UNBLOCK. Fixes: . Approved by Marco Trevisan (Treviño). + +2012-02-29 Thomi Richards <thomi.richards@canonical.com> + + Renamed run_autopilot to just 'autopilot'. This avoids the situation where we type 'run_autopilot run'. + +2012-02-29 Thomi Richards <thomi.richards@canonical.com> + + Can now run specific tests. + +2012-02-29 Thomi Richards <thomi.richards@canonical.com> + + run_autopilot can now list tests as well as run the entire suite. + +2012-02-29 Thomi Richards <thomi.richards@canonical.com> + + Moved run_autopilot script, made it report missing dependancies, and made it patch sys.path if needed. + +2012-02-28 Tim Penhey <tim.penhey@canonical.com> + + Fix logic determining icons for alt-tab.. Fixes: . Approved by Thomi Richards. + +2012-02-29 Tim Penhey <tim.penhey@canonical.com> + + Use the compiz keybinding to minimise. + +2012-02-29 Tim Penhey <tim.penhey@canonical.com> + + Fix the AP test. + +2012-02-29 Tim Penhey <tim.penhey@canonical.com> + + The method for determining whether to show only current workspace had the logic wrong. + +2012-02-28 Thomi Richards <thomi.richards@canonical.com> + + Adds autopilot tests for jason's branch that fixes a bug regarding switching to minimised apps.. Fixes: . Approved by Tim Penhey. + +2012-02-28 Thomi Richards <thomi.richards@canonical.com> + + Removed print debugging statement. + +2012-02-28 Thomi Richards <thomi.richards@canonical.com> + + Merged Jason's branch with the fix. Tests now pass. + +2012-02-22 Jason Smith <jason.smith@canonical.com> + + fix issue where windows wouldn't unminimize with alt-tab + +2012-02-28 Thomi Richards <thomi.richards@canonical.com> + + Failing test. + +2012-02-28 Thomi Richards <thomi.richards@canonical.com> + + Merged multi-monitor alt+tab tests, as I need some of the infrastructure here. + +2012-02-28 Thomi Richards <thomi.richards@canonical.com> + + Can now launch gedit easily from within tests, and can see if a BamfWindow is focused or not. + +2012-02-28 Thomi Richards <thomi.richards@canonical.com> + + Added autopilot tests for the switcher "show apps from all workspaces" mode.. Fixes: . Approved by Tim Penhey. + +2012-02-28 Thomi Richards <thomi.richards@canonical.com> + + Fixed typo in comment. + +2012-02-28 Thomi Richards <thomi.richards@canonical.com> + + Fixed an error in the swticher tests, and relaxed the keybindings module WRT getting hold and tap parts of a single-key binding. + +2012-02-28 Thomi Richards <thomi.richards@canonical.com> + + Merged trunk. + +2012-02-28 Thomi Richards <thomi.richards@canonical.com> + + Updated keybindings for switcher in 'all' mode. + +2012-02-28 Thomi Richards <thomi.richards@canonical.com> + + Merged branch that stops trunk from crashing. This has been approved, but not merged yet. + +2012-02-28 Thomi Richards <thomi.richards@canonical.com> + + Merged Jason's branch with the alt-tab-on-all-workspaces fix. + +2012-02-22 Jason Smith <jason.smith@canonical.com> + + merge trunk + +2012-02-07 Thomi Richards <thomi.richards@canonical.com> + + Added small timeout to launcher tests to get them to pass. + +2012-02-06 Jason Smith <jason.smith@canonical.com> + + better scaling + +2012-02-06 Jason Smith <jason.smith@canonical.com> + + scale reveal pressure by screen size + +2012-02-06 Jason Smith <jason.smith@canonical.com> + + re-enable and fix launcher reveal tests + +2012-02-06 Jason Smith <jason.smith@canonical.com> + + merge trunk + +2012-02-01 Jason Smith <jason.smith@canonical.com> + + implement ctrl+alt+tab to show apps on all viewports + +2012-02-28 Thomi Richards <thomi.richards@canonical.com> + + Failing AP test - switcher does not show apps from all workspaces. + +2012-02-28 Thomi Richards <thomi.richards@canonical.com> + + Added test to ensure that switcher only shows icons from current workspace. + +2012-02-28 Thomi Richards <thomi.richards@canonical.com> + + We now have a basic workspace manager - we can switch between workspaces at will. + +2012-02-28 Thomi Richards <thomi.richards@canonical.com> + + Added keybindings from the wall plugin. + +2012-02-28 Thomi Richards <thomi.richards@canonical.com> + + Merged trunk. + +2012-02-28 Thomi Richards <thomi.richards@canonical.com> + + Added file for workspace manager. + +2012-02-27 Thomi Richards <thomi.richards@canonical.com> + + Added a manual test to describe how to test bug lp:932365. + + The code has already been merged for this, but it's currently pretty hard to test with autopilot.. Fixes: . Approved by Tim Penhey. + +2012-02-28 Thomi Richards <thomi.richards@canonical.com> + + Updated manual test - it works with a device icon too. + +2012-02-28 Thomi Richards <thomi.richards@canonical.com> + + Added manual test for dragging an icon over the trash in the launcher. + +2012-02-27 Tim Penhey <tim.penhey@canonical.com> + + Code correctness fix. + Fixes potential multi-monitor issue with alt-tab getting settings confused. + + = Problem description = + + We passed an int into a function expecting a bool. This caused the switcher to confuse its settings and act weirdly. + + = The fix = + + We pass a bool now. + + = Test coverage = + + Existing tests cover a degree of this behavior. Currently testing does not cover multi-monitor behavior however (which is where this bug showed up). The fix is obvious and the current code is clearly and demonstrably wrong, to the point of passing the wrong type into a function. + + A manual test was added for the multi-monitor work.. Fixes: . Approved by Thomi Richards. + +2012-02-28 Tim Penhey <tim.penhey@canonical.com> + + Tweak the test. + +2012-02-28 Tim Penhey <tim.penhey@canonical.com> + + Add a manual multi-monitor test. + +2012-02-22 Jason Smith <jason.smith@canonical.com> + + dont pass an int as a bool, this is obviously wrong (and buggy) + +2012-02-27 Thomi Richards <thomi.richards@canonical.com> + + Don't call std::list::remove while iterating over the list. + + This bug was found while running a soon-to-be-landed autopilot test.. Fixes: . Approved by Tim Penhey, Jason Smith. + +2012-02-28 Thomi Richards <thomi.richards@canonical.com> + + Revert rev 2018, since it causes crashes in the switcher. + +2012-02-27 Thomi Richards <thomi.richards@canonical.com> + + Re-added the switcher tests after they were accidentally lost in a recent merge.. Fixes: . Approved by Jason Smith. + +2012-02-28 Thomi Richards <thomi.richards@canonical.com> + + Re-added switcher tests that got lost in a merge. + +2012-02-27 Thomi Richards <thomi.richards@canonical.com> + + Launcher does not reveal if mouse button 1 is held down while pushing against screen edge. Fixes lp:928805.. Fixes: . Approved by Jason Smith. + +2012-02-28 Thomi Richards <thomi.richards@canonical.com> + + Fixed test name and docstring to match test contents. + +2012-02-28 Thomi Richards <thomi.richards@canonical.com> + + Merged fix branch. Test passes. + +2012-02-21 Jason Smith <jason.smith@canonical.com> + + dont reveal when mouse is down + +2012-02-28 Thomi Richards <thomi.richards@canonical.com> + + Have a failing test. + +2012-02-28 Thomi Richards <thomi.richards@canonical.com> + + Moved launcher reveal tests to separate test case class. + +2012-02-27 Thomi Richards <thomi.richards@canonical.com> + + Change autopilot so instead of hardcoding keybindings, we get them from compiz.. Fixes: . Approved by Tim Penhey. + +2012-02-28 Thomi Richards <thomi.richards@canonical.com> + + Improved error handling for optional paramter. + +2012-02-28 Thomi Richards <thomi.richards@canonical.com> + + Added an optional delay parameter to KeybindingsHelper.keybinding so the hud emulator can use it as well. + +2012-02-28 Thomi Richards <thomi.richards@canonical.com> + + Dash emulator uses KeybindingsHelper class. + +2012-02-28 Thomi Richards <thomi.richards@canonical.com> + + Launcher emulator uses keybindingsHelper class. + +2012-02-28 Thomi Richards <thomi.richards@canonical.com> + + Switcher emulator uses KeybindingsHelper class. + +2012-02-28 Thomi Richards <thomi.richards@canonical.com> + + Switcher emulator changed to use new keybindings system. + +2012-02-28 Thomi Richards <thomi.richards@canonical.com> + + Hud emulator converted to new keybindings system. + +2012-02-28 Thomi Richards <thomi.richards@canonical.com> + + Dash emulator converted to use new keybindings system. + +2012-02-28 Thomi Richards <thomi.richards@canonical.com> + + Finished switching launcher to new keybindings system. Refactored keybindings internals to use a single dictionary. Made it easier to get the hold part and the tap part of a multi-key keybindings (like Alt+Tab) + +2012-02-27 Thomi Richards <thomi.richards@canonical.com> + + Seems to work... + +2012-02-27 Thomi Richards <thomi.richards@canonical.com> + + Added keybindings module. About to start adding it to the emulators. + +2012-02-27 Thomi Richards <thomi.richards@canonical.com> + + Renamed function, changed test to use an import alias so this is easier in the future. + +2012-02-27 Thomi Richards <thomi.richards@canonical.com> + + Moved to keybindings module. + +2012-02-27 Thomi Richards <thomi.richards@canonical.com> + + Fixed some whitespace and added one more test, for good luck. + +2012-02-27 Thomi Richards <thomi.richards@canonical.com> + + All tests pass. + +2012-02-27 Thomi Richards <thomi.richards@canonical.com> + + Test passes. + +2012-02-27 Thomi Richards <thomi.richards@canonical.com> + + Failing test: identical keys must not be duplicated in output. + +2012-02-27 Thomi Richards <thomi.richards@canonical.com> + + TEst Passes. + +2012-02-27 Thomi Richards <thomi.richards@canonical.com> + + Failing test: <Control> -> Ctrl + +2012-02-27 Thomi Richards <thomi.richards@canonical.com> + + Test passes. + +2012-02-27 Thomi Richards <thomi.richards@canonical.com> + + Failing test: must strip trailing whitespace from imput string. + +2012-02-27 Thomi Richards <thomi.richards@canonical.com> + + Test now passes. + +2012-02-27 Thomi Richards <thomi.richards@canonical.com> + + Failing test - single letter translation. + +2012-02-27 Thomi Richards <thomi.richards@canonical.com> + + Refactored - translation function should take strings, and return strings. + +2012-02-27 Thomi Richards <thomi.richards@canonical.com> + + Test now passes. + +2012-02-27 Thomi Richards <thomi.richards@canonical.com> + + Failing test: only setting instances with type=Key should be allowed. + +2012-02-27 Thomi Richards <thomi.richards@canonical.com> + + Test passes. + +2012-02-27 Thomi Richards <thomi.richards@canonical.com> + + Failing test case: must raise TypeError if something other than a compizconfig.Setting is passed in. + +2012-02-27 Thomi Richards <thomi.richards@canonical.com> + + Added tests for the command lens. + + Specifically: + * Empty string query should hide the results category. + * Non-empty string should show the results category. + * Non-empty string should show results within the results category. + * Hitting enter before the view has had time to refresh should run the correct result. + * Ensure that expanding or collapsing the filter bar does not remove keyboard focus from the searchbar.. Fixes: . Approved by Tim Penhey. + +2012-02-27 Thomi Richards <thomi.richards@canonical.com> + + Merged trunk. + +2012-02-27 Thomi Richards <thomi.richards@canonical.com> + + Removed whitespace + +2012-02-24 Thomi Richards <thomi.richards@canonical.com> + + Removed manual test specs that are now tested automatically. + +2012-02-24 Thomi Richards <thomi.richards@canonical.com> + + Added test to ensure that keyboard focus is not lost when expanding or collapsing the filter bar. + +2012-02-24 Thomi Richards <thomi.richards@canonical.com> + + Merged trunk. + +2012-02-24 Thomi Richards <thomi.richards@canonical.com> + + Added x,y,widtha nd height properties to FilterBar. + +2012-02-23 Thomi Richards <thomi.richards@canonical.com> + + Merged trunk. + +2012-02-23 Thomi Richards <thomi.richards@canonical.com> + + Removed unused import. + +2012-02-23 Thomi Richards <thomi.richards@canonical.com> + + Added test that verifies correct app is run when Enter is pressed before the view can update. + +2012-02-23 Thomi Richards <thomi.richards@canonical.com> + + Forgot to add the file previously. Added test file with 3 new tests for the command lens. + +2012-02-23 Thomi Richards <thomi.richards@canonical.com> + + Added ResultsView to introspection tree. + +2012-02-23 Thomi Richards <thomi.richards@canonical.com> + + Basic results test. + +2012-02-27 Thomi Richards <thomi.richards@canonical.com> + + Re-enabled launcher reveal tests, and set launcher mode to "autohide" for these tests. + Made it significantly easier to set any compiz/unity option for a single test. These options get automatically un-set when the test finishes.. Fixes: . Approved by Jason Smith. + +2012-02-27 Thomi Richards <thomi.richards@canonical.com> + + Removed stray pdb statement. + +2012-02-27 Thomi Richards <thomi.richards@canonical.com> + + Merged trunk. + +2012-02-27 Thomi Richards <thomi.richards@canonical.com> + + Re-enabled launcher tests. Made it easier to set any unity /compiz option for a single test. + +2012-02-26 Thomi Richards <thomi.richards@canonical.com> + + Added autopilot tests to verify that the switcher moves to the next switcher icon when Tab is pressed while in details mode. + + Switcher tests for details mode are "scenario"'d so we cover details mode initiated by both '`' and 'Down' keys.. Fixes: . Approved by Tim Penhey. + +2012-02-27 Thomi Richards <thomi.richards@canonical.com> + + Merged Jason's branch, verified that switcher tests passed. + +2012-02-21 Jason Smith <jason.smith@canonical.com> + + make pressing the down arrow not result in window looping + +2012-02-27 Thomi Richards <thomi.richards@canonical.com> + + Have a failing test case - pressing tab after initiating details mode with the 'Down' arrow does not select next item in the switcher model. + +2012-02-27 Thomi Richards <thomi.richards@canonical.com> + + Added tests to verify that the switcher starts in normal mode, and that pressing '`' starts details mode. + +2012-02-27 Thomi Richards <thomi.richards@canonical.com> + + Added test to ensure that hitting down arrow puts switcher into details mode. + +2012-02-26 Tim Penhey <tim.penhey@canonical.com> + + This branch just refactors some autopilot code. + + Extracts the log handling code for the test into a different base class. + + Also makes bamf and keyboard an attribute of all autopilot tests. + + Adds a helper method to start an app.. Fixes: . Approved by Thomi Richards. + +2012-02-27 Tim Penhey <tim.penhey@canonical.com> + + Make a Bamf instance a member of they base test case. + +2012-02-27 Tim Penhey <tim.penhey@canonical.com> + + Extract the logger setup into a base class. + +2012-02-26 Thomi Richards <thomi.richards@canonical.com> + + Autopilot ibus tests now use new autopilot API for getting dash searchbar object.. Fixes: . Approved by Tim Penhey. + +2012-02-27 Thomi Richards <thomi.richards@canonical.com> + + Updated ibus tests to match new autopilot API. + +2012-02-26 Thomi Richards <thomi.richards@canonical.com> + + IBus test suites that don't have the required engine installed will no longer fail with an error on tearDown. Instead, the test will be skipped with a helpful message (as originally intended). + + Also updated the scenario names for the Anthy engine tests.. Fixes: . Approved by Robert Carr. + +2012-02-26 Thomi Richards <thomi.richards@canonical.com> + + IBus tests suites no longer fail if the ibus engine is missing. The tests will be skipped instead. + +2012-02-26 Thomi Richards <thomi.richards@canonical.com> + + Forgot to change scenario names in Anthy tests. + +2012-02-25 Jeremy Bicha <jbicha@ubuntu.com> + + . Fixes: https://bugs.launchpad.net/bugs/926213. Approved by Marco Trevisan (Treviño), Matthew Paul Thomas, Sam Spilsbury. + +2012-02-03 Jeremy Bicha <jbicha@ubuntu.com> + + Overlay should say "Menu Bar" not "Top Bar" (LP: #926213) + +2012-02-25 Jason Smith <jason.smith@canonical.com> + + = The Problem = + Alt-Tab timeouts are too long + + = The Fix = + Make the timeout 75ms instead of 150ms + + = Testing = + The existing switcher autopilot tests cover the expected behaviour alt-tab behaviour.. Fixes: https://bugs.launchpad.net/bugs/888636. Approved by David Barth. + +2012-02-22 Jason Smith <jason.smith@canonical.com> + + update alt-tab timeout time to match design requirements + +2012-02-24 Paul Sladen <sladen@canonical.com> + + No glow updates and associate code/asset removal (LP: #933578). Fixes: https://bugs.launchpad.net/bugs/933578. Approved by Gord Allott, Andrea Cimitan. + +2012-02-24 Paul Sladen <sladen@canonical.com> + + Remove unused glow assets + +2012-02-24 Paul Sladen <sladen@canonical.com> + + ABI break: remove now unused Glow variables and helper functions + +2012-02-24 Paul Sladen <sladen@canonical.com> + + No glow: Dash search: remove Glow texture drawing + (leave functions themselves in order not to break ABI) + +2012-02-24 Alex Launi <alex.launi@canonical.com> + + Overrides the Add and RemoveChild methods of Introspectable for more readable code in the ResultView.. Fixes: . Approved by Gord Allott. + +2012-02-24 Alex Launi <alex.launi@canonical.com> + + Add IntrospectableWrapper to standalone dash CMakeList + +2012-02-24 Alex Launi <alex.launi@canonical.com> + + refactor dash result introspection to use standard introspection api + +2012-02-23 Brandon Schaefer <brandontschaefer@gmail.com + + = Problem description = + + Fixes middle mouse button pasting, which was removed by me. + + = The fix = + + Restored the IMTextEntry::OnMouseButtonUp function but now it only handles paste with the middle mouse + + = Test coverage = + + There is a manual test for this. Fixes: https://bugs.launchpad.net/bugs/926793. Approved by Thomi Richards. + +2012-02-23 Brandon Schaefer <brandontschaefer@gmail.com + + Changed indent + Also now using manual test for middle mouse paste + +2012-02-23 Brandon Schaefer <brandontschaefer@gmail.com + + Test for clipboard added, + Still need to finish Primary Paste + +2012-02-23 Brandon Schaefer <brandontschaefer@gmail.com + + Fixes the problem where you can't paste multiple times + +2012-02-23 Brandon Schaefer <brandontschaefer@gmail.com + + Adds test for middle click pasting + Adds Test for copy/paste crash bug:926793 + +2012-02-23 Brandon Schaefer <brandontschaefer@gmail.com + + Re-fixes middle paste, which was removed by me + +2012-02-23 Thomi Richards <thomi.richards@canonical.com> + + Added tests for ibus strings being entered into the dash. Engines tested include Pinyin (Chinese), Anthy (Japanese), and Hangul (Korean).. Fixes: . Approved by Alex Launi. + +2012-02-23 Thomi Richards <thomi.richards@canonical.com> + + Wait a bit after restarting the ibus bus. + +2012-02-22 Thomi Richards <thomi.richards@canonical.com> + + Restart ibus bus after setting a new input method engine. + +2012-02-22 Thomi Richards <thomi.richards@canonical.com> + + Get the engine name right.. again. + +2012-02-22 Thomi Richards <thomi.richards@canonical.com> + + Japanese engine doesn't put spaces in... + +2012-02-22 Thomi Richards <thomi.richards@canonical.com> + + Get the engine name right\! + +2012-02-22 Thomi Richards <thomi.richards@canonical.com> + + Now with tests for the anthi input engine. + +2012-02-22 Thomi Richards <thomi.richards@canonical.com> + + Hangul input strings append a space to the expected result. Updated tests. + +2012-02-22 Thomi Richards <thomi.richards@canonical.com> + + Better class docstrings, and encode all unicode in u'\uXXXX' format. + +2012-02-22 Thomi Richards <thomi.richards@canonical.com> + + Added tests for hangul input engine. + +2012-02-22 Thomi Richards <thomi.richards@canonical.com> + + I'm an idiot + +2012-02-22 Thomi Richards <thomi.richards@canonical.com> + + AP tests now use testscenarios. + +2012-02-21 Thomi Richards <thomi.richards@canonical.com> + + Now activate ibus after loading dash. + +2012-02-21 Thomi Richards <thomi.richards@canonical.com> + + CLeaned up ibus dash tests. + +2012-02-21 Thomi Richards <thomi.richards@canonical.com> + + No longer use the global ibus engine. Instead, install engines into the active engine list. + +2012-02-17 Thomi Richards <thomi.richards@canonical.com> + + Updated ibus tests from Martin's branch. + +2012-02-16 Thomi Richards <thomi.richards@canonical.com> + + Fixed ibus tests. + +2012-02-16 Thomi Richards <thomi.richards@canonical.com> + + Remember to add new files. + +2012-02-16 Thomi Richards <thomi.richards@canonical.com> + + Added ibus tests. + +2012-02-23 Andrea Cimitan <andrea.cimitan@canonical.com> + + New asses from design, looks nicer!. Fixes: . Approved by Marco Trevisan (Treviño), Andrea Cimitan. + +2012-02-23 Andrea Cimitan <andrea.cimitan@canonical.com> + + New BFB shine + +2012-02-23 Lars Uebernickel <lars.uebernickel@canonical.com> + + = Problem description = + + In precise, print status is shown via an indicator instead of using system-config-printer's applet (which is whitelisted in Unity right now). + + = The fix = + + This patch makes sure the print indicator is shown at the correct position in the panel and removes the whitelist entry for 'scp-dbus-service'. + + = Test coverage = + + None, trivial change. + . Fixes: . Approved by Gord Allott. + +2012-02-22 Lars Uebernickel <lars.uebernickel@canonical.com> + + Add indicator-printers support and remove whitelist entry of old printer applet + +2012-02-23 Jason Smith <jason.smith@canonical.com> + + = The Problem = + + Slide animation wasn't smooth when dragging icons around ont he launcher + + = The Fix = + + Make the animation take into account the parents geometry offset when calculating the animation + + = Testing = + + None. Fixes: . Approved by Gord Allott. + +2012-02-22 Jason Smith <jason.smith@canonical.com> + + Fix calculation of slide animation to account for parent geometry + +2012-02-23 Didier Roche <didier.roche@canonical.com> + + Change F10 to open the first menu of top panel to Alt + F10 as per design (LP: #878492). Fixes: https://bugs.launchpad.net/bugs/878492. Approved by Tim Penhey. + +2012-02-23 Didier Roche <didier.roche@canonical.com> + + Change F10 to open the first menu of top panel to Alt + F10 as per design (LP: #878492) + +2012-02-23 Brandon Schaefer <brandontschaefer@gmail.com + + IMTextEntry now only handles copy/cut and pasting, everything else is in nux::TextEntry. Fixes: https://bugs.launchpad.net/bugs/880876. Approved by Tim Penhey, Jay Taoko, Thomi Richards. + +2012-02-23 Brandon Schaefer <brandontschaefer@gmail.com + + merged trunk, fixed conflicts + +2012-02-22 Brandon Schaefer <brandontschaefer@gmail.com + + removed ibus dep check + +2012-02-22 Brandon Schaefer <brandontschaefer@gmail.com + + TextEntry was replaced with TextEntryIM + +2012-02-22 Jay Taoko <jay.taoko@canonical.com> + + + * Using signal TextEntryIM::text_changed. + +2012-02-22 Brandon Schaefer <brandontschaefer@gmail.com + + TextEntryIM now using NUX_TRACKER_LOCATION, instead of hard coding it in + +2012-02-22 Brandon Schaefer <brandontschaefer@gmail.com + + Merged trunk + +2012-02-20 Brandon Schaefer <brandontschaefer@gmail.com + + merged trunk + +2012-02-20 Brandon Schaefer <brandontschaefer@gmail.com + + Fixes the preedit box disappearing because it never knew dash lost key focus + +2012-02-17 Brandon Schaefer <brandontschaefer@gmail.com + + Fixed double pasting + +2012-02-17 Brandon Schaefer <brandontschaefer@gmail.com + + merge + +2012-02-16 Brandon Schaefer <brandontschaefer@gmail.com + + Fixes crash in IMTextEntry + IMTextEntry now only handles copying/cuting/pasting + New nux TextEntryIM being used for now + +2012-02-23 Tim Penhey <tim.penhey@canonical.com> + + = Problem description = + + Failed to compile with the new nux as the signal for text_changed had changed. + + = The fix = + + Rename the signal + + = Test coverage = + + Clean compile.. Fixes: . Approved by Mirco Müller. + +2012-02-23 Tim Penhey <tim.penhey@canonical.com> + + New signals. + +2012-02-22 Thomi Richards <thomi.richards@canonical.com> + + Split the dash keynav test into several smaller tests that test one specific thing. + + Also removed an unused import from the dash emulator.. Fixes: . Approved by Tim Penhey. + +2012-02-23 Thomi Richards <thomi.richards@canonical.com> + + Split several dash tests into multiple, smaller tests. + +2012-02-22 Thomi Richards <thomi.richards@canonical.com> + + Refactored the autopilot dash emulator to use multiple classes instead of one massive one. + + Also removed several instances where XPath queries were being hard-coded. Updated tests to use new class structure.. Fixes: . Approved by . + +2012-02-23 Thomi Richards <thomi.richards@canonical.com> + + Refactoring finished. + +2012-02-23 Thomi Richards <thomi.richards@canonical.com> + + Refactor finished, all tests pass. + +2012-02-23 Thomi Richards <thomi.richards@canonical.com> + + Refactor half done. All tests pass. + +2012-02-22 Gord Allott <gord.allott@canonical.com> + + Makes bghash only change the gsettings key once per wallpaper change, instead of on every frame it transitions to the next wallpaper. + + No test as gsettings is not sandboxable and this change is *only* gsettings key change related.. Fixes: . Approved by Mirco Müller. + +2012-02-22 Gord Allott <gord.allott@canonical.com> + + makes bghash only change the gsettings setting where it makes sense instead of clobbering it every frame of animation + +2012-02-22 Gord Allott <gord.allott@canonical.com> + + Enables -Werror. Fixes: . Approved by Michal Hruby. + +2012-02-22 Gord Allott <gord.allott@canonical.com> + + re-enables -Werror - should never have been turned off + +2012-02-22 Michal Hruby <michal.mhr@gmail.com> + + Splits out the .lens file reading out of FilesystemLenses class and exposes it in LensDirectoryReader, this allows the BFB to directly check the lens files from the disk and we don't start up the lenses as soon as unity starts. + + Un-reverts the revert done in r2001. + + The extremely complex part for unity-2d is @ https://code.launchpad.net/~mhr3/unity-2d/default-filesystemlenses-constructor/+merge/94102 + + Covered by existing tests.. Fixes: https://bugs.launchpad.net/bugs/929506. Approved by Gord Allott. + +2012-02-22 Michal Hruby <michal.mhr@gmail.com> + + Unrevert the revert of r2001 + +2012-02-21 Jason Smith <jason.smith@canonical.com> + + Restores accidentally commented code. This should have been uncommented before merge previously, but was missed. Fixes bug where launcher didn't properly reflect window states on initial startup. Fixes: . Approved by Tim Penhey. + +2012-02-21 Jason Smith <jason.smith@canonical.com> + + Check launcher icon state on creation again. Was accidentally left commented when porting to AbstractLauncherIcon::Ptr in rev 1948.8.1 + +2012-02-21 Thomi Richards <thomi.richards@canonical.com> + + Clean up result wrappers when resultview exits.. Fixes: . Approved by Tim Penhey. + +2012-02-22 Thomi Richards <thomi.richards@canonical.com> + + Clean up wrappers on destruction of ResultsView. + +2012-02-22 Thomi Richards <thomi.richards@canonical.com> + + Clean up wrappers on destruction of ResultsView. + +2012-02-21 Thomi Richards <thomi.richards@canonical.com> + + Make results from dash searches introspectable in autopilot.. Fixes: . Approved by Alex Launi. + +2012-02-22 Thomi Richards <thomi.richards@canonical.com> + + Don't take comment from Result. + +2012-02-22 Thomi Richards <thomi.richards@canonical.com> + + Merged trunk. + +2012-02-22 Thomi Richards <thomi.richards@canonical.com> + + Don't need '.c_str()' when adding std::strings to the variant::BuilderWrapper class. + +2012-02-21 Thomi Richards <thomi.richards@canonical.com> + + Merged trunk. + +2012-02-21 Thomi Richards <thomi.richards@canonical.com> + + Committing before merging trunk to get NString removal code. + +2012-02-21 Thomi Richards <thomi.richards@canonical.com> + + Merged trunk. + +2012-02-21 Thomi Richards <thomi.richards@canonical.com> + + Unity now wraps dash results during introspection. + +2012-02-21 Thomi Richards <thomi.richards@canonical.com> + + LensView now adds a couple of useful properties. + +2012-02-21 Thomi Richards <thomi.richards@canonical.com> + + ResultView added to introspection tree. + +2012-02-21 Thomi Richards <thomi.richards@canonical.com> + + Added ResultView class. + +2012-02-21 Thomi Richards <thomi.richards@canonical.com> + + Revert revision 1999 since it breaks unity2d.. Fixes: . Approved by Jay Taoko. + +2012-02-22 Thomi Richards <thomi.richards@canonical.com> + + Revert revision 1999 since it breaks unity2d. + +2012-02-21 Gord Allott <gord.allott@canonical.com> + + Adds a gdbus proxy test suite. Fixes: . Approved by Michal Hruby. + +2012-02-21 Gord Allott <gord.allott@canonical.com> + + and the other sources + +2012-02-21 Gord Allott <gord.allott@canonical.com> + + remove the source, oops + +2012-02-21 Gord Allott <gord.allott@canonical.com> + + fix up the tests so they pass + +2012-02-21 Gord Allott <gord.allott@canonical.com> + + adds a gdbus test and service + +2012-02-21 Michal Hruby <michal.mhr@gmail.com> + + Splits out the .lens file reading out of FilesystemLenses class and exposes it in LensDirectoryReader, this allows the BFB to directly check the lens files from the disk and we don't start up the lenses as soon as unity starts.. Fixes: https://bugs.launchpad.net/bugs/929506. Approved by Gord Allott. + +2012-02-21 Michal Hruby <michal.mhr@gmail.com> + + Default setting for Visible key is true + +2012-02-21 Michal Hruby <michal.mhr@gmail.com> + + Move LensFileData struct into LensDirectoryReader + +2012-02-21 Michal Hruby <michal.mhr@gmail.com> + + Merge trunk + +2012-02-21 Michal Hruby <michal.mhr@gmail.com> + + Refactor FilesystemLenses class + +2012-02-21 Andrea Azzarone <azzaronea@gmail.com> + + Depends on https://code.launchpad.net/~andyrock/nux/focus-on-enter + + Implements dash key navigation. + + known issues: + - shift + tab doesn't work well because of this: bug 931393. Fixes: https://bugs.launchpad.net/bugs/817436, https://bugs.launchpad.net/bugs/844033, https://bugs.launchpad.net/bugs/891648. Approved by John Lea, Tim Penhey. + +2012-02-21 Andrea Azzarone <azzaronea@gmail.com> + + Focus on enter for rating filter too. + +2012-02-21 Andrea Azzarone <azzaronea@gmail.com> + + Merge trunk. + +2012-02-21 Andrea Azzarone <azzaronea@gmail.com> + + Implement rating filter key navigation. + +2012-02-21 Andrea Azzarone <azzaronea@gmail.com> + + Fix AP test. + +2012-02-21 Andrea Azzarone <azzaronea@gmail.com> + + Add an AP test for Tab. + +2012-02-20 Andrea Azzarone <azzaronea@gmail.com> + + Add a partial test for Tab. Need to commit to merge a branch. + +2012-02-20 Andrea Azzarone <azzaronea@gmail.com> + + Add an autopilot test for the ctlr + tab. + +2012-02-20 Andrea Azzarone <azzaronea@gmail.com> + + Merge trunk. + +2012-02-20 Andrea Azzarone <azzaronea@gmail.com> + + Merge trunk + +2012-02-17 Andrea Azzarone <azzaronea@gmail.com> + + Fix focus on enter. + +2012-02-17 Andrea Azzarone <azzaronea@gmail.com> + + Merge trunk. + +2012-02-17 Andrea Azzarone <azzaronea@gmail.com> + + Focus on mouse enter. + +2012-02-14 Andrea Azzarone <azzaronea@gmail.com> + + Don't focus on mouse down. + +2012-02-14 Andrea Azzarone <azzaronea@gmail.com> + + Merge trunk. + +2012-02-14 Andrea Azzarone <azzaronea@gmail.com> + + Ops. + +2012-02-14 Andrea Azzarone <azzaronea@gmail.com> + + Fix backward key navigation. + +2012-02-13 Andrea Azzarone <azzaronea@gmail.com> + + Fix. + +2012-02-13 Andrea Azzarone <azzaronea@gmail.com> + + Merge trunk. + +2012-02-13 Andrea Azzarone <azzaronea@gmail.com> + + Fix. + +2012-02-13 Andrea Azzarone <azzaronea@gmail.com> + + Cltr+tab and tab key navigation. + +2012-02-13 Andrea Azzarone <azzaronea@gmail.com> + + Merge trunk. + +2012-02-13 Andrea Azzarone <azzaronea@gmail.com> + + Implements filters key navigation. + +2012-02-13 Andrea Azzarone <azzaronea@gmail.com> + + Merge trunk. + +2012-02-13 Andrea Azzarone <azzaronea@gmail.com> + + Merge trunk. Make "Filter results focusable". Fix ResultGridView key navigation. + +2012-02-21 Jason Smith <jason.smith@canonical.com> + + fix bug where icons would jump around when resorting over an icon not in their own group + + Launcher still lacks any significant method of being tested for these kinds of changes. Significant refactoring is still required.. Fixes: https://bugs.launchpad.net/bugs/932365. Approved by Thomi Richards. + +2012-02-21 Jason Smith <jason.smith@canonical.com> + + make sure we dont re-sort icons that are of different types as they wont be allowed to mingle anyhow + +2012-02-21 Jason Smith <jason.smith@canonical.com> + + Fixes an issue where the launcher icons would seem to "dance" back and forth when re-ordering by dragging. Fixes: . Approved by Sam Spilsbury. + +2012-02-21 Jason Smith <jason.smith@canonical.com> + + fix re-order thrashing + +2012-02-21 Thomi Richards <thomi.richards@canonical.com> + + Removed calls to g_variant_builder_add in favor of using the variant::BuilderWrapper class in UnityCore.. Fixes: . Approved by Jason Smith. + +2012-02-21 Thomi Richards <thomi.richards@canonical.com> + + Forgot to add #include. + +2012-02-21 Thomi Richards <thomi.richards@canonical.com> + + Merged trunk, fixed one place with g_variant calls. + +2012-02-21 Thomi Richards <thomi.richards@canonical.com> + + Removed a bunch of g_variant_builder_add calls. + +2012-02-20 Jason Smith <jason.smith@canonical.com> + + Ensures that icons start from the proper location when dragging. Fixes: . Approved by Tim Penhey. + +2012-02-20 Jason Smith <jason.smith@canonical.com> + + make drag icons start from the pointer location properly + +2012-02-20 Thomi Richards <thomi.richards@canonical.com> + + LauncherIcon, StaticCairoText and Tooltip classes now use std::string rather than nux::NString.. Fixes: . Approved by Tim Penhey, Jason Smith. + +2012-02-21 Thomi Richards <thomi.richards@canonical.com> + + Const keyword reordering. + +2012-02-21 Thomi Richards <thomi.richards@canonical.com> + + Cleanups. + +2012-02-21 Thomi Richards <thomi.richards@canonical.com> + + SCT takes a std::string in it's ctor now. + +2012-02-21 Thomi Richards <thomi.richards@canonical.com> + + Removed NString from StaticCairoText, Tooltip and LauncherIcon classes. + +2012-02-21 Thomi Richards <thomi.richards@canonical.com> + + Removed NString from QuicklistView. + +2012-02-20 Thomi Richards <thomi.richards@canonical.com> + + Fixed a warning about member variable order in initialiser list in PanelMenuView class.. Fixes: . Approved by Tim Penhey. + +2012-02-21 Thomi Richards <thomi.richards@canonical.com> + + Fix a warning about bad order in initialiser list in PanelMenuView class. + +2012-02-20 Gabor Kelemen <kelemeng@ubuntu.com> + + . Fixes: https://bugs.launchpad.net/bugs/930510. Approved by Tim Penhey. + +2012-02-18 Gabor Kelemen <kelemeng@ubuntu.com> + + Mark some forgotten strings for translation. LP: 930510 + +2012-02-20 Thomi Richards <thomi.richards@canonical.com> + + Update the unity introspection tree debug script to use the new autopilot API.. Fixes: . Approved by Tim Penhey, Andrea Azzarone. + +2012-02-20 Thomi Richards <thomi.richards@canonical.com> + + Updated unity introspection tree display script to use new autopilot API. + +2012-02-20 Mirco Müller <mirco.mueller@canonical.com> + + Fixed artwork-assets to be correctly cropped to intended size. Replaced procedural vector drawing methods with newly added artwork-textures. Updated sizes and margins to adapt to new artwork. Added FIXME-hint for keyboard-navigation highlight implementation. + + Also see size/gap verification in gimp for screenshot of newly implemented rating-stars: + http://people.canonical.com/~mmueller/fix-924884.png. Fixes: https://bugs.launchpad.net/bugs/924884. Approved by Andrea Azzarone, Andrea Cimitan. + +2012-02-20 Mirco Müller <mirco.mueller@canonical.com> + + Added new artwork-assets for rating-stars. Replace old procedural vector-drawing with new textures. Adjusted sizes and gaps/margins. Added fixme-hint for later keyboard-navigation highlight implementation. + +2012-02-20 Andrea Azzarone <azzaronea@gmail.com> + + Update the design for the lensbar focus highlight. + + Mockup: https://chinstrap.canonical.com/~sabdfl/12_04/desktop_and_netbook/dash/tweaks_dash.png + Branch: http://ubuntuone.com/4Y6KetbWzMYcofmTbftCih. Fixes: . Approved by Andrea Cimitan, John Lea, Xi Zhu. + +2012-02-09 Andrea Azzarone <azzaronea@gmail.com> + + Fixes. + +2012-02-08 Andrea Azzarone <azzaronea@gmail.com> + + Update AGAIN the design for the lensbar focus highlight. + Reintroduce the triangle that indicates the active lens too. + +2012-02-20 Michal Hruby <michal.mhr@gmail.com> + + Makes sure we properly emit the connected signal.. Fixes: . Approved by Gord Allott. + +2012-02-20 Michal Hruby <michal.mhr@gmail.com> + + Fix incorrect proxy state + +2012-02-20 alanbell@ubuntu.com + + the dependency on the largedesktop feature which can be provided by wall or cube is a nice idea, but in practice this means that when changing from one to the other the unity plugin gets unloaded half way through which breaks peoples desktops. Without the dependency the transition works fine.. Fixes: . Approved by Didier Roche. + +2012-02-18 alanbell@ubuntu.com + + dropped dependency on largedesktop feature, it doesn't need it except for the switcher and users changing to cube from wall end up deselecting unity, this makes it safer. + +2012-02-20 Thomi Richards <thomi.richards@canonical.com> + + Add an autopilot test to verify that multiple Hud reveals don't leave the launcher in a bad state.. Fixes: . Approved by Tim Penhey. + +2012-02-20 Thomi Richards <thomi.richards@canonical.com> + + Removed unused import. + +2012-02-20 Thomi Richards <thomi.richards@canonical.com> + + Wrote failing test for hud multiple-reveals. + +2012-02-19 Jay Taoko <jay.taoko@canonical.com> + + * Triggers Unity build following Nux abi changes.. Fixes: . Approved by Jay Taoko. + +2012-02-19 Jay Taoko <jay.taoko@canonical.com> + + + * Triggers Unity build following Nux abi changes. + +2012-02-17 Didier Roche <didier.roche@canonical.com> + + Release\ 5.4.0 + 2012-02-17 Marco Trevisan (Treviño) <mail@3v1n0.net> Fix crash on missing dash WindowButton files @@ -1,5 +1,5 @@ If you want to hack on unity you need the following packages - - nux + - libnux-2.0 - libbamf - libdee - gio-2.0 @@ -18,3 +18,4 @@ Makes the panel run the unity-panel-service directly instead of through D-Bus activation. This is used for testing how the panel reacts when it starts before the service does. + diff --git a/UnityCore/FilesystemLenses.cpp b/UnityCore/FilesystemLenses.cpp index 82dd16784..42a6d53bd 100644 --- a/UnityCore/FilesystemLenses.cpp +++ b/UnityCore/FilesystemLenses.cpp @@ -46,39 +46,33 @@ const char* GROUP = "Lens"; } // Loads data from a Lens key-file in a usable form -struct LensFileData -{ - LensFileData(GKeyFile* file) - : domain(g_key_file_get_string(file, G_KEY_FILE_DESKTOP_GROUP, "X-Ubuntu-Gettext-Domain", NULL)) - , dbus_name(g_key_file_get_string(file, GROUP, "DBusName", NULL)) - , dbus_path(g_key_file_get_string(file, GROUP, "DBusPath", NULL)) - , name(g_strdup(g_dgettext(domain.Value(), g_key_file_get_string(file, GROUP, "Name", NULL)))) - , icon(g_key_file_get_string(file, GROUP, "Icon", NULL)) - , description(g_key_file_get_locale_string(file, GROUP, "Description", NULL, NULL)) - , search_hint(g_key_file_get_locale_string(file, GROUP, "SearchHint", NULL, NULL)) - , visible(g_key_file_get_boolean(file, GROUP, "Visible", NULL)) - , shortcut(g_key_file_get_string(file, GROUP, "Shortcut", NULL)) - {} - - static bool IsValid(GKeyFile* file, glib::Error& error) +LensDirectoryReader::LensFileData::LensFileData(GKeyFile* file, + const gchar *lens_id) + : id(g_strdup(lens_id)) + , domain(g_key_file_get_string(file, G_KEY_FILE_DESKTOP_GROUP, "X-Ubuntu-Gettext-Domain", NULL)) + , dbus_name(g_key_file_get_string(file, GROUP, "DBusName", NULL)) + , dbus_path(g_key_file_get_string(file, GROUP, "DBusPath", NULL)) + , name(g_strdup(g_dgettext(domain.Value(), g_key_file_get_string(file, GROUP, "Name", NULL)))) + , icon(g_key_file_get_string(file, GROUP, "Icon", NULL)) + , description(g_key_file_get_locale_string(file, GROUP, "Description", NULL, NULL)) + , search_hint(g_key_file_get_locale_string(file, GROUP, "SearchHint", NULL, NULL)) + , visible(true) + , shortcut(g_key_file_get_string(file, GROUP, "Shortcut", NULL)) +{ + if (g_key_file_has_key(file, GROUP, "Visible", NULL)) { - return (g_key_file_has_group(file, GROUP) && - g_key_file_has_key(file, GROUP, "DBusName", &error) && - g_key_file_has_key(file, GROUP, "DBusPath", &error) && - g_key_file_has_key(file, GROUP, "Name", &error) && - g_key_file_has_key(file, GROUP, "Icon", &error)); + visible = g_key_file_get_boolean(file, GROUP, "Visible", NULL) != FALSE; } +} - glib::String domain; - glib::String dbus_name; - glib::String dbus_path; - glib::String name; - glib::String icon; - glib::String description; - glib::String search_hint; - bool visible; - glib::String shortcut; -}; +bool LensDirectoryReader::LensFileData::IsValid(GKeyFile* file, glib::Error& error) +{ + return (g_key_file_has_group(file, GROUP) && + g_key_file_has_key(file, GROUP, "DBusName", &error) && + g_key_file_has_key(file, GROUP, "DBusPath", &error) && + g_key_file_has_key(file, GROUP, "Name", &error) && + g_key_file_has_key(file, GROUP, "Icon", &error)); +} /* A quick guide to finding Lens files * @@ -103,92 +97,57 @@ struct LensFileData * override those found system-wide. This is to ease development of Lenses. * */ -class FilesystemLenses::Impl + +class LensDirectoryReader::Impl { public: typedef std::map<GFile*, glib::Object<GCancellable>> CancellableMap; - Impl(FilesystemLenses* owner); - Impl(FilesystemLenses* owner, std::string const& lens_directory); - - ~Impl(); + Impl(LensDirectoryReader *owner, std::string const& directory) + : owner_(owner) + , directory_(g_file_new_for_path(directory.c_str())) + , children_waiting_to_load_(0) + , enumeration_done_(false) + { + LOG_DEBUG(logger) << "Initialising lens reader for: " << directory; + + glib::Object<GCancellable> cancellable(g_cancellable_new()); + g_file_enumerate_children_async(directory_, + G_FILE_ATTRIBUTE_STANDARD_NAME, + G_FILE_QUERY_INFO_NONE, + G_PRIORITY_DEFAULT, + cancellable, + (GAsyncReadyCallback)OnDirectoryEnumerated, + this); + cancel_map_[directory_] = cancellable; + } - LensList GetLenses() const; - Lens::Ptr GetLens(std::string const& lens_id) const; - Lens::Ptr GetLensAtIndex(std::size_t index) const; - Lens::Ptr GetLensForShortcut(std::string const& lens_shortcut) const; - std::size_t count() const; + ~Impl() + { + for (auto pair: cancel_map_) + { + g_cancellable_cancel(pair.second); + } + } - void Init(); - glib::Object<GFile> BuildLensPathFile(std::string const& directory); void EnumerateLensesDirectoryChildren(GFileEnumerator* enumerator); void LoadLensFile(std::string const& lensfile_path); - void CreateLensFromKeyFileData(GFile* path, const char* data, gsize length); - void DecrementAndCheckChildrenWaiting(); + void GetLensDataFromKeyFile(GFile* path, const char* data, gsize length); + DataList GetLensData() const; + void SortLensList(); static void OnDirectoryEnumerated(GFile* source, GAsyncResult* res, Impl* self); static void LoadFileContentCallback(GObject* source, GAsyncResult* res, gpointer user_data); - FilesystemLenses* owner_; + LensDirectoryReader *owner_; glib::Object<GFile> directory_; + DataList lenses_data_; std::size_t children_waiting_to_load_; + bool enumeration_done_; CancellableMap cancel_map_; - LensList lenses_; }; -FilesystemLenses::Impl::Impl(FilesystemLenses* owner) - : owner_(owner) - , children_waiting_to_load_(0) -{ - LOG_DEBUG(logger) << "Initialising in standard lens directory mode: " << LENSES_DIR; - - directory_ = BuildLensPathFile(LENSES_DIR); - - Init(); -} - -FilesystemLenses::Impl::Impl(FilesystemLenses* owner, std::string const& lens_directory) - : owner_(owner) - , children_waiting_to_load_(0) -{ - LOG_DEBUG(logger) << "Initialising in override lens directory mode"; - - directory_ = g_file_new_for_path(lens_directory.c_str()); - - Init(); -} - -FilesystemLenses::Impl::~Impl() -{ - for (auto pair: cancel_map_) - { - g_cancellable_cancel(pair.second); - } -} - -void FilesystemLenses::Impl::Init() -{ - glib::String path(g_file_get_path(directory_)); - LOG_DEBUG(logger) << "Searching for Lenses in: " << path; - - glib::Object<GCancellable> cancellable(g_cancellable_new()); - g_file_enumerate_children_async(directory_, - G_FILE_ATTRIBUTE_STANDARD_NAME, - G_FILE_QUERY_INFO_NONE, - G_PRIORITY_DEFAULT, - cancellable, - (GAsyncReadyCallback)OnDirectoryEnumerated, - this); - cancel_map_[directory_] = cancellable; -} - -glib::Object<GFile> FilesystemLenses::Impl::BuildLensPathFile(std::string const& directory) -{ - glib::Object<GFile> file(g_file_new_for_path(directory.c_str())); - return file; -} - -void FilesystemLenses::Impl::OnDirectoryEnumerated(GFile* source, GAsyncResult* res, Impl* self) +void LensDirectoryReader::Impl::OnDirectoryEnumerated(GFile* source, GAsyncResult* res, Impl* self) { glib::Error error; glib::Object<GFileEnumerator> enumerator(g_file_enumerate_children_finish(source, res, error.AsOutParam())); @@ -196,43 +155,59 @@ void FilesystemLenses::Impl::OnDirectoryEnumerated(GFile* source, GAsyncResult* if (error || !enumerator) { glib::String path(g_file_get_path(source)); - LOG_WARN(logger) << "Unabled to enumerate children of directory " + LOG_WARN(logger) << "Unable to enumerate children of directory " << path << " " << error; return; } - self->EnumerateLensesDirectoryChildren(enumerator); self->cancel_map_.erase(source); + self->EnumerateLensesDirectoryChildren(enumerator); } -void FilesystemLenses::Impl::EnumerateLensesDirectoryChildren(GFileEnumerator* enumerator) +void LensDirectoryReader::Impl::EnumerateLensesDirectoryChildren(GFileEnumerator* in_enumerator) { - glib::Error error; - glib::Object<GFileInfo> info; + glib::Object<GCancellable> cancellable(g_cancellable_new()); - while (info = g_file_enumerator_next_file(enumerator, NULL, error.AsOutParam())) - { - if (info && !error) + cancel_map_[g_file_enumerator_get_container(in_enumerator)] = cancellable; + g_file_enumerator_next_files_async (in_enumerator, 64, G_PRIORITY_DEFAULT, + cancellable, + [] (GObject *src, GAsyncResult *res, + gpointer data) -> void { + // async callback + glib::Error error; + GFileEnumerator *enumerator = G_FILE_ENUMERATOR (src); + // FIXME: won't this kill the enumerator? + GList *files = g_file_enumerator_next_files_finish (enumerator, res, error.AsOutParam()); + if (!error) { - std::string name(g_file_info_get_name(info)); - glib::String dir_path(g_file_get_path(g_file_enumerator_get_container(enumerator))); - std::string lensfile_name = name + ".lens"; - - glib::String lensfile_path(g_build_filename(dir_path.Value(), - name.c_str(), - lensfile_name.c_str(), - NULL)); - LoadLensFile(lensfile_path.Str()); + Impl *self = (Impl*) data; + self->cancel_map_.erase(g_file_enumerator_get_container(enumerator)); + for (GList *iter = files; iter; iter = iter->next) + { + glib::Object<GFileInfo> info((GFileInfo*) iter->data); + + std::string name(g_file_info_get_name(info)); + glib::String dir_path(g_file_get_path(g_file_enumerator_get_container(enumerator))); + std::string lensfile_name = name + ".lens"; + + glib::String lensfile_path(g_build_filename(dir_path.Value(), + name.c_str(), + lensfile_name.c_str(), + NULL)); + self->LoadLensFile(lensfile_path.Str()); + } + // the GFileInfos got already freed during the iteration + g_list_free (files); + self->enumeration_done_ = true; } else { LOG_WARN(logger) << "Cannot enumerate over directory: " << error; - continue; } - } + }, this); } -void FilesystemLenses::Impl::LoadLensFile(std::string const& lensfile_path) +void LensDirectoryReader::Impl::LoadLensFile(std::string const& lensfile_path) { glib::Object<GFile> file(g_file_new_for_path(lensfile_path.c_str())); glib::Object<GCancellable> cancellable(g_cancellable_new()); @@ -242,14 +217,14 @@ void FilesystemLenses::Impl::LoadLensFile(std::string const& lensfile_path) g_file_load_contents_async(file, cancellable, - (GAsyncReadyCallback)(FilesystemLenses::Impl::LoadFileContentCallback), + (GAsyncReadyCallback)(LensDirectoryReader::Impl::LoadFileContentCallback), this); cancel_map_[file] = cancellable; } -void FilesystemLenses::Impl::LoadFileContentCallback(GObject* source, - GAsyncResult* res, - gpointer user_data) +void LensDirectoryReader::Impl::LoadFileContentCallback(GObject* source, + GAsyncResult* res, + gpointer user_data) { Impl* self = static_cast<Impl*>(user_data); glib::Error error; @@ -263,7 +238,8 @@ void FilesystemLenses::Impl::LoadFileContentCallback(GObject* source, NULL, error.AsOutParam()); if (result && !error) { - self->CreateLensFromKeyFileData(file, contents.Value(), length); + self->GetLensDataFromKeyFile(file, contents.Value(), length); + self->SortLensList(); } else { @@ -272,41 +248,18 @@ void FilesystemLenses::Impl::LoadFileContentCallback(GObject* source, << error; } - self->DecrementAndCheckChildrenWaiting(); self->cancel_map_.erase(file); -} -void FilesystemLenses::Impl::DecrementAndCheckChildrenWaiting() -{ // If we're not waiting for any more children to load, signal that we're // done reading the directory - children_waiting_to_load_--; - if (!children_waiting_to_load_) + self->children_waiting_to_load_--; + if (self->children_waiting_to_load_ == 0) { - //FIXME: This should be it's own function, but we're trying not to break ABI - // right now. - //FIXME: We don't have a strict order, but alphabetical serves us wonderfully for - // Oneiric. When we have an order/policy, please replace this. - auto sort_cb = [] (Lens::Ptr a, Lens::Ptr b) -> bool - { - if (a->id == "applications.lens") - return true; - else if (b->id == "applications.lens") - return false; - else - return g_strcmp0(a->id().c_str(), b->id().c_str()) < 0; - }; - std::sort(lenses_.begin(), - lenses_.end(), - sort_cb); - for (Lens::Ptr& lens: lenses_) - owner_->lens_added.emit(lens); - - owner_->lenses_loaded.emit(); + self->owner_->load_finished.emit(); } } -void FilesystemLenses::Impl::CreateLensFromKeyFileData(GFile* file, +void LensDirectoryReader::Impl::GetLensDataFromKeyFile(GFile* file, const char* data, gsize length) { @@ -318,19 +271,9 @@ void FilesystemLenses::Impl::CreateLensFromKeyFileData(GFile* file, { if (LensFileData::IsValid(key_file, error)) { - LensFileData data(key_file); glib::String id(g_path_get_basename(path.Value())); - Lens::Ptr lens(new Lens(id.Str(), - data.dbus_name.Str(), - data.dbus_path.Str(), - data.name.Str(), - data.icon.Str(), - data.description.Str(), - data.search_hint.Str(), - data.visible, - data.shortcut.Str())); - lenses_.push_back(lens); + lenses_data_.push_back(new LensFileData(key_file, id)); LOG_DEBUG(logger) << "Sucessfully loaded lens file " << path; } @@ -350,6 +293,122 @@ void FilesystemLenses::Impl::CreateLensFromKeyFileData(GFile* file, g_key_file_free(key_file); } +LensDirectoryReader::DataList LensDirectoryReader::Impl::GetLensData() const +{ + return lenses_data_; +} + +void LensDirectoryReader::Impl::SortLensList() +{ + //FIXME: We don't have a strict order, but alphabetical serves us well. + // When we have an order/policy, please replace this. + auto sort_cb = [] (LensFileData* a, LensFileData* b) -> bool + { + if (a->id.Str() == "applications.lens") + return true; + else if (b->id.Str() == "applications.lens") + return false; + else + return g_strcmp0(a->id.Value(), b->id.Value()) < 0; + }; + std::sort(lenses_data_.begin(), + lenses_data_.end(), + sort_cb); +} + +LensDirectoryReader::LensDirectoryReader(std::string const& directory) + : pimpl(new Impl(this, directory)) +{ +} + +LensDirectoryReader::~LensDirectoryReader() +{ + delete pimpl; +} + +LensDirectoryReader::Ptr LensDirectoryReader::GetDefault() +{ + static LensDirectoryReader::Ptr main_reader(new LensDirectoryReader(LENSES_DIR)); + + return main_reader; +} + +bool LensDirectoryReader::IsDataLoaded() const +{ + return pimpl->children_waiting_to_load_ == 0 && pimpl->enumeration_done_; +} + +LensDirectoryReader::DataList LensDirectoryReader::GetLensData() const +{ + return pimpl->GetLensData(); +} + +class FilesystemLenses::Impl +{ +public: + Impl(FilesystemLenses* owner, LensDirectoryReader::Ptr const& reader); + ~Impl() + { + if (timeout_id != 0) g_source_remove (timeout_id); + } + + void OnLoadingFinished(); + + LensList GetLenses() const; + Lens::Ptr GetLens(std::string const& lens_id) const; + Lens::Ptr GetLensAtIndex(std::size_t index) const; + Lens::Ptr GetLensForShortcut(std::string const& lens_shortcut) const; + std::size_t count() const; + + FilesystemLenses* owner_; + LensDirectoryReader::Ptr reader_; + LensList lenses_; + guint timeout_id; +}; + +FilesystemLenses::Impl::Impl(FilesystemLenses* owner, LensDirectoryReader::Ptr const& reader) + : owner_(owner) + , reader_(reader) + , timeout_id(0) +{ + reader_->load_finished.connect(sigc::mem_fun(this, &Impl::OnLoadingFinished)); + if (reader_->IsDataLoaded()) + { + // we won't get any signal, so let's just emit our signals after construction + timeout_id = g_idle_add_full (G_PRIORITY_DEFAULT, + [] (gpointer data) -> gboolean { + Impl *self = (Impl*) data; + self->timeout_id = 0; + self->OnLoadingFinished(); + return FALSE; + }, + this, NULL); + } +} + +void FilesystemLenses::Impl::OnLoadingFinished() +{ + // FIXME: clear lenses_ first? + for (auto lens_data : reader_->GetLensData()) + { + Lens::Ptr lens(new Lens(lens_data->id, + lens_data->dbus_name, + lens_data->dbus_path, + lens_data->name, + lens_data->icon, + lens_data->description, + lens_data->search_hint, + lens_data->visible, + lens_data->shortcut)); + lenses_.push_back (lens); + } + + for (Lens::Ptr& lens: lenses_) + owner_->lens_added.emit(lens); + + owner_->lenses_loaded.emit(); +} + Lenses::LensList FilesystemLenses::Impl::GetLenses() const { return lenses_; @@ -401,13 +460,13 @@ std::size_t FilesystemLenses::Impl::count() const FilesystemLenses::FilesystemLenses() - : pimpl(new Impl(this)) + : pimpl(new Impl(this, LensDirectoryReader::GetDefault())) { Init(); } -FilesystemLenses::FilesystemLenses(std::string const& lens_directory) - : pimpl(new Impl(this, lens_directory)) +FilesystemLenses::FilesystemLenses(LensDirectoryReader::Ptr const& reader) + : pimpl(new Impl(this, reader)) { Init(); } diff --git a/UnityCore/FilesystemLenses.h b/UnityCore/FilesystemLenses.h index c6acad6c1..e5b2087da 100644 --- a/UnityCore/FilesystemLenses.h +++ b/UnityCore/FilesystemLenses.h @@ -1,6 +1,6 @@ // -*- Mode: C++; indent-tabs-mode: nil; tab-width: 2 -*- /* - * Copyright (C) 2011 Canonical Ltd + * Copyright (C) 2011-2012 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3 as @@ -31,6 +31,44 @@ namespace unity namespace dash { +class LensDirectoryReader : public sigc::trackable +{ +public: + struct LensFileData + { + LensFileData(GKeyFile* file, const gchar *lens_id); + static bool IsValid(GKeyFile* file, glib::Error& error); + + glib::String id; + glib::String domain; + glib::String dbus_name; + glib::String dbus_path; + glib::String name; + glib::String icon; + glib::String description; + glib::String search_hint; + bool visible; + glib::String shortcut; + }; + + typedef std::shared_ptr<LensDirectoryReader> Ptr; + typedef std::vector<LensFileData*> DataList; + + LensDirectoryReader(std::string const& directory); + ~LensDirectoryReader(); + + static LensDirectoryReader::Ptr GetDefault(); + + bool IsDataLoaded() const; + DataList GetLensData() const; + + sigc::signal<void> load_finished; + +private: + class Impl; + Impl* pimpl; +}; + // Reads Lens information from the filesystem, as per-specification, and creates // Lens instances using this data class FilesystemLenses : public Lenses @@ -39,9 +77,7 @@ public: typedef std::shared_ptr<FilesystemLenses> Ptr; FilesystemLenses(); - FilesystemLenses(std::string const& lens_directory); - - void Init(); + FilesystemLenses(LensDirectoryReader::Ptr const& reader); ~FilesystemLenses(); @@ -53,6 +89,8 @@ public: sigc::signal<void> lenses_loaded; private: + void Init(); + class Impl; Impl* pimpl; }; diff --git a/UnityCore/GLibDBusProxy.cpp b/UnityCore/GLibDBusProxy.cpp index bf77b0b08..e75a3d123 100644 --- a/UnityCore/GLibDBusProxy.cpp +++ b/UnityCore/GLibDBusProxy.cpp @@ -142,6 +142,12 @@ void DBusProxy::Impl::OnNameAppeared(GDBusConnection* connection, { DBusProxy::Impl* self = static_cast<DBusProxy::Impl*>(impl); LOG_DEBUG(logger) << self->name_ << " appeared"; + + if (self->proxy_) + { + self->connected_ = true; + self->owner_->connected.emit(); + } } void DBusProxy::Impl::OnNameVanished(GDBusConnection* connection, diff --git a/UnityCore/Hud.h b/UnityCore/Hud.h index 01acdf666..452f099f9 100644 --- a/UnityCore/Hud.h +++ b/UnityCore/Hud.h @@ -24,7 +24,7 @@ #include <string> #include <memory> #include <NuxCore/Property.h> -#include <glib/gvariant.h> +#include <glib.h> namespace unity { diff --git a/UnityCore/Variant.cpp b/UnityCore/Variant.cpp index fe8c96452..5e50651ed 100644 --- a/UnityCore/Variant.cpp +++ b/UnityCore/Variant.cpp @@ -142,6 +142,12 @@ BuilderWrapper& BuilderWrapper::add(char const* name, int value) return *this; } +BuilderWrapper& BuilderWrapper::add(char const* name, unsigned value) +{ + g_variant_builder_add(builder_, "{sv}", name, g_variant_new_uint32(value)); + return *this; +} + BuilderWrapper& BuilderWrapper::add(char const* name, float value) { // floats get promoted to doubles automatically diff --git a/UnityCore/Variant.h b/UnityCore/Variant.h index 8001cdc09..aa74921cc 100644 --- a/UnityCore/Variant.h +++ b/UnityCore/Variant.h @@ -73,6 +73,7 @@ public: BuilderWrapper& add(char const* name, char const* value); BuilderWrapper& add(char const* name, std::string const& value); BuilderWrapper& add(char const* name, int value); + BuilderWrapper& add(char const* name, unsigned value); BuilderWrapper& add(char const* name, float value); BuilderWrapper& add(char const* name, GVariant* value); BuilderWrapper& add(nux::Rect const& value); diff --git a/com.canonical.Unity.gschema.xml b/com.canonical.Unity.gschema.xml index 7d9603ac2..607302332 100644 --- a/com.canonical.Unity.gschema.xml +++ b/com.canonical.Unity.gschema.xml @@ -40,7 +40,7 @@ </schema> <schema path="/desktop/unity/panel/" id="com.canonical.Unity.Panel" gettext-domain="unity"> <key type="as" name="systray-whitelist"> - <default>[ 'JavaEmbeddedFrame', 'Wine', 'scp-dbus-service', 'Update-notifier' ]</default> + <default>[ 'JavaEmbeddedFrame', 'Wine', 'Update-notifier' ]</default> <summary>List of client names, resource classes or wm classes to allow in the Panel's systray implementation.</summary> <description>"" (empty) will not allow any tray icons, "all" will allow all tray icons, otherwise there will be an attempt to match each icon to a value here.</description> </key> diff --git a/debian/changelog b/debian/changelog index 9ae74d776..d6fa19aa9 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,9 @@ +unity (5.6.0-0ubuntu1) UNRELEASED; urgency=low + + * New upstream release. + + -- Didier Roche <didrocks@ubuntu.com> Mon, 12 Mar 2012 10:18:19 +0100 + unity (5.4.0-0ubuntu2) precise; urgency=low * 01_hardcode_new_default_in_hardcoded_values.patch: diff --git a/manual-tests/Dash.txt b/manual-tests/Dash.txt index f4309be6e..740cd7e4f 100644 --- a/manual-tests/Dash.txt +++ b/manual-tests/Dash.txt @@ -1,32 +1,16 @@ -Dash search ------------ -This test makes sure that the right command is run when you search -using the dash. (see lp:856205) - -#. Press Alt+F2 -#. Press 'g'. Make sure you see some command name (like gcc) -#. Quickly type 'edit<Enter>' - so you'd run 'gedit'. - -Outcome - The dash disappears, and gedit is run. If nothing happens or the first - command is run (in this case gcc), this test failed. - - Mouse down and search bar focus ------------------------------- This test makes sure that the search bar doesn't lose the focus clicking into am emtpy area (or into a filter button). - + #. Open the app lens #. Expand the filter bar -#. Make sure the dash has the focus -#. The cursor should blink #. Click between a filter expander and the All Button (for example between Type > and All) or into a filter button. Outcome The cursor is still blinking. - + Category headers focus ---------------------- @@ -38,8 +22,8 @@ This test makes sure that clicking on a category headers doesn't focus it. Outcome The category should expand/collaspe and the search bar still has the focus. - - + + Drag and drop dash icons ---------------------------- This test makes sure that. @@ -67,15 +51,3 @@ Outcome The text previously selected is pasted on the search bar at mouse pointer position, if the operation is repeated the text is inserted where the mouse pointer is. - -Dash SearchBar IBus Focus ------------------------------- -This test shows that the IBus is getting focus when it gets keyboard focus. - -#. Open the Dash -#. Press Ctrl + Space to activate ibus -#. Type "abc1" - -Outcome - Typing "abc1" while ibus is focused will produce 阿布从 . If the ibus is not focused - then "abc1" will be the outcome (This is incorrect behavior). diff --git a/manual-tests/Launcher.txt b/manual-tests/Launcher.txt index 92af6f5a8..1aa7e2335 100644 --- a/manual-tests/Launcher.txt +++ b/manual-tests/Launcher.txt @@ -84,3 +84,19 @@ This Test shows that clicking on a quicklist option quits keynav mode. Outcome No matter what option you click will exit keynav mode. +Drag Icons to Trash +------------------- + +This test verifies that dragging icons to the trash removes them from the +launcher, and that the animation is smooth. + +#. Move mouse pointer over an application or device icon (not BFB, Workspace switcher or trash) +#. Hold mouse button down until icon attached to moue pointer (1 Second or so) +#. With the mouse button held down, drag the icon over the trash icon. +#. Release the mouse pointer. + +Outcome: + * The icon should be removed from the launcher. + * The trash can icon should remain stationary during the entire operation. + * The operation should NOT LOOK LIKE THIS: https://bugs.launchpad.net/unity/+bug/932365/+attachment/2739868/+files/out-2.ogv + diff --git a/manual-tests/SuperTab.txt b/manual-tests/SuperTab.txt index 95d512194..24a746ea1 100644 --- a/manual-tests/SuperTab.txt +++ b/manual-tests/SuperTab.txt @@ -21,6 +21,7 @@ Outcome: when the Super+Tab is activated and the icons should be expanded when neeeded. + Super Tab switcher interaction with Shortcut Hint ------------------------------------------------- This test shows the interaction between the shortcut hint overlay and the @@ -43,26 +44,6 @@ Outcome: Super+Tab switcher is initialized and the shortcut hint overlay is not shown even keeping only super pressed until releasing it and pressing it again. -Super Tab switcher cycling over launcher applications ------------------------------------------------------ -This test shows how the Super+Tab switcher should use a circular selection. - -#. Start with a clean screen -#. Press Super+Tab multiple times to make the selection to reach the bottom - launcher icon -#. Press Tab again - -Outcome: - The selection should go from the last launcher icon, to the first one. - The launcher view should be expanded/moved if needed to show the highlighted item. - -#. Start with a clean screen -#. Press Super+Tab once to make the first launcher icon to highlight -#. Press Shift+Tab while keeping Super pressed - -Outcome: - The selection should go from the first launcher icon to the last one. - The launcher view should be expanded/moved if needed to show the highlighted item. Escaping from Super Tab switcher -------------------------------- diff --git a/manual-tests/Switcher.txt b/manual-tests/Switcher.txt new file mode 100644 index 000000000..40cda2693 --- /dev/null +++ b/manual-tests/Switcher.txt @@ -0,0 +1,18 @@ +Multi-monitor Alt-Tab +--------------------- +This test only applies for multiple monitor. +App names here are an example, any apps will do. + +#. Start with no apps open (or at least remember what is open where) +#. Start terminal on workspace 1 +#. Start firefox on workspace 2 +#. Move to workspace 2 + +Now do the following on both monitors + +#. Hold Alt, and press Tab, hold and observe +#. Release Alt. + +Outcomes + While alt is held, Firefox should appear in the tab list, and + terminal should not diff --git a/plugins/unityshell/resources/search_close_glow.png b/plugins/unityshell/resources/search_close_glow.png Binary files differdeleted file mode 100644 index 86c18f831..000000000 --- a/plugins/unityshell/resources/search_close_glow.png +++ /dev/null diff --git a/plugins/unityshell/resources/search_spin_glow.png b/plugins/unityshell/resources/search_spin_glow.png Binary files differdeleted file mode 100644 index 329bbe48e..000000000 --- a/plugins/unityshell/resources/search_spin_glow.png +++ /dev/null diff --git a/plugins/unityshell/resources/squircle_shine_54.png b/plugins/unityshell/resources/squircle_shine_54.png Binary files differindex 34f20dc31..2b2f1a574 100644 --- a/plugins/unityshell/resources/squircle_shine_54.png +++ b/plugins/unityshell/resources/squircle_shine_54.png diff --git a/plugins/unityshell/resources/star_deselected.png b/plugins/unityshell/resources/star_deselected.png Binary files differnew file mode 100644 index 000000000..fb3ca3674 --- /dev/null +++ b/plugins/unityshell/resources/star_deselected.png diff --git a/plugins/unityshell/resources/star_highlight.png b/plugins/unityshell/resources/star_highlight.png Binary files differnew file mode 100644 index 000000000..3319d1520 --- /dev/null +++ b/plugins/unityshell/resources/star_highlight.png diff --git a/plugins/unityshell/resources/star_selected.png b/plugins/unityshell/resources/star_selected.png Binary files differnew file mode 100644 index 000000000..27be91284 --- /dev/null +++ b/plugins/unityshell/resources/star_selected.png diff --git a/plugins/unityshell/src/AggregateMonitor.cpp b/plugins/unityshell/src/AggregateMonitor.cpp index 207a14fa8..62eb5c2e8 100644 --- a/plugins/unityshell/src/AggregateMonitor.cpp +++ b/plugins/unityshell/src/AggregateMonitor.cpp @@ -19,6 +19,7 @@ #include "AggregateMonitor.h" #include "ElapsedTimeMonitor.h" +#include <UnityCore/Variant.h> namespace unity { namespace performance { @@ -49,11 +50,12 @@ void AggregateMonitor::StartMonitor() void AggregateMonitor::StopMonitor(GVariantBuilder* builder) { + variant::BuilderWrapper wrapper(builder); for (std::list<Monitor*>::iterator iter = _monitors.begin(), end = _monitors.end(); iter != end; ++iter) { Monitor* monitor = *iter; - g_variant_builder_add(builder, "{sv}", monitor->GetName().c_str(), monitor->Stop()); + wrapper.add(monitor->GetName().c_str(), monitor->Stop()); } } diff --git a/plugins/unityshell/src/BFBLauncherIcon.cpp b/plugins/unityshell/src/BFBLauncherIcon.cpp index 07f736a90..0d1abdfd8 100644 --- a/plugins/unityshell/src/BFBLauncherIcon.cpp +++ b/plugins/unityshell/src/BFBLauncherIcon.cpp @@ -34,6 +34,7 @@ UBusManager BFBLauncherIcon::ubus_manager_; BFBLauncherIcon::BFBLauncherIcon() : SimpleLauncherIcon() + , reader_(dash::LensDirectoryReader::GetDefault()) { tooltip_text = _("Dash home"); icon_name = PKGDATADIR"/launcher_bfb.png"; @@ -94,21 +95,21 @@ std::list<DbusmenuMenuitem*> BFBLauncherIcon::GetMenus() result.push_back(menu_item); // Other lenses.. - for (auto lens : lenses_.GetLenses()) + for (auto lens : reader_->GetLensData()) { - if (!lens->visible()) + if (!lens->visible) continue; menu_item = dbusmenu_menuitem_new(); - dbusmenu_menuitem_property_set(menu_item, DBUSMENU_MENUITEM_PROP_LABEL, lens->name().c_str()); + dbusmenu_menuitem_property_set(menu_item, DBUSMENU_MENUITEM_PROP_LABEL, lens->name); dbusmenu_menuitem_property_set_bool(menu_item, DBUSMENU_MENUITEM_PROP_ENABLED, true); dbusmenu_menuitem_property_set_bool(menu_item, DBUSMENU_MENUITEM_PROP_VISIBLE, true); g_signal_connect(menu_item, DBUSMENU_MENUITEM_SIGNAL_ITEM_ACTIVATED, (GCallback)&BFBLauncherIcon::OnMenuitemActivated, - g_strdup(lens->id().c_str())); + g_strdup(lens->id)); result.push_back(menu_item); } diff --git a/plugins/unityshell/src/BFBLauncherIcon.h b/plugins/unityshell/src/BFBLauncherIcon.h index 1db9aeaa1..d81de4828 100644 --- a/plugins/unityshell/src/BFBLauncherIcon.h +++ b/plugins/unityshell/src/BFBLauncherIcon.h @@ -51,7 +51,7 @@ private: static unity::UBusManager ubus_manager_; nux::Color background_color_; - dash::FilesystemLenses lenses_; + dash::LensDirectoryReader::Ptr reader_; }; } diff --git a/plugins/unityshell/src/BGHash.cpp b/plugins/unityshell/src/BGHash.cpp index 5e7cc63f6..b6b7bcc6d 100644 --- a/plugins/unityshell/src/BGHash.cpp +++ b/plugins/unityshell/src/BGHash.cpp @@ -344,6 +344,22 @@ namespace unity { _hires_time_end = 500 * 1000; // 500 milliseconds _transition_handler = g_timeout_add (1000/60, (GSourceFunc)BGHash::OnTransitionCallback, this); + // export to gsettings + GSettings* settings = NULL; + GdkColor color = {0, + (guint16) (_new_color.red * 65535.0 * 0.7f), + (guint16) (_new_color.green * 65535.0 * 0.7f), + (guint16) (_new_color.blue * 65535.0 * 0.7f)}; + + settings = g_settings_new (UNITY_SCHEMA.c_str()); + if (settings) + { + unity::glib::String color_string(gdk_color_to_string(&color)); + LOG_DEBUG(logger) << "Setting gsettings key to: " << color_string; + g_settings_set_string(settings, AVG_BG_COLOR.c_str(), color_string); + g_object_unref (settings); + } + } gboolean BGHash::OnTransitionCallback(BGHash *self) @@ -384,19 +400,6 @@ namespace unity { _current_color.blue * 0.7f, 0.5) ); - GSettings* settings = NULL; - GdkColor color = {0, - (guint16) (_current_color.red * 65535.0 * 0.7f), - (guint16) (_current_color.green * 65535.0 * 0.7f), - (guint16) (_current_color.blue * 65535.0 * 0.7f)}; - - settings = g_settings_new (UNITY_SCHEMA.c_str()); - if (settings) - { - unity::glib::String color_string(gdk_color_to_string(&color)); - g_settings_set_string(settings, AVG_BG_COLOR.c_str(), color_string); - g_object_unref (settings); - } } GdkPixbuf *BGHash::GetPixbufFromBG () diff --git a/plugins/unityshell/src/BamfLauncherIcon.cpp b/plugins/unityshell/src/BamfLauncherIcon.cpp index 5695a0340..84acd2068 100644 --- a/plugins/unityshell/src/BamfLauncherIcon.cpp +++ b/plugins/unityshell/src/BamfLauncherIcon.cpp @@ -20,6 +20,7 @@ #include <Nux/Nux.h> #include <Nux/BaseWindow.h> +#include <UnityCore/Variant.h> #include "BamfLauncherIcon.h" #include "FavoriteStore.h" @@ -122,7 +123,7 @@ BamfLauncherIcon::BamfLauncherIcon(BamfApplication* app) WindowManager::Default()->compiz_screen_viewport_switch_ended.connect(sigc::mem_fun(this, &BamfLauncherIcon::EnsureWindowState)); WindowManager::Default()->terminate_expo.connect(sigc::mem_fun(this, &BamfLauncherIcon::EnsureWindowState)); - //EnsureWindowState(); + EnsureWindowState(); UpdateMenus(); UpdateDesktopFile(); @@ -440,10 +441,7 @@ void BamfLauncherIcon::AddProperties(GVariantBuilder* builder) { LauncherIcon::AddProperties(builder); - g_variant_builder_add(builder, "{sv}", "desktop-file", g_variant_new_string(DesktopFile().c_str())); - GList* children, *l; - children = bamf_view_get_children(BAMF_VIEW(_bamf_app.RawPtr())); GVariant* xids[(int) g_list_length(children)]; @@ -457,8 +455,11 @@ void BamfLauncherIcon::AddProperties(GVariantBuilder* builder) xids[i++] = g_variant_new_uint32(xid); } g_list_free(children); - g_variant_builder_add(builder, "{sv}", "xids", g_variant_new_array(G_VARIANT_TYPE_UINT32, xids, i)); - g_variant_builder_add(builder, "{sv}", "sticky", g_variant_new_boolean(IsSticky())); + + variant::BuilderWrapper(builder) + .add("desktop-file", DesktopFile()) + .add("xids", g_variant_new_array(G_VARIANT_TYPE_UINT32, xids, i)) + .add("sticky", IsSticky()); } bool BamfLauncherIcon::OwnsWindow(Window xid) const @@ -662,64 +663,50 @@ void BamfLauncherIcon::EnsureWindowState() void BamfLauncherIcon::UpdateDesktopQuickList() { - GKeyFile* keyfile; - glib::Error error; std::string const& desktop_file = DesktopFile(); if (desktop_file.empty()) return; - // check that we have the X-Ayatana-Desktop-Shortcuts flag - // not sure if we should do this or if libindicator should shut up - // and not report errors when it can't find the key. - // so FIXME when ted is around - keyfile = g_key_file_new(); - g_key_file_load_from_file(keyfile, desktop_file.c_str(), G_KEY_FILE_NONE, &error); - - if (error) - { - g_warning("Could not load desktop file for: %s", desktop_file.c_str()); - g_key_file_free(keyfile); - return; - } - - if (g_key_file_has_key(keyfile, G_KEY_FILE_DESKTOP_GROUP, - "X-Ayatana-Desktop-Shortcuts", nullptr)) - { - for (GList *l = dbusmenu_menuitem_get_children(_menu_desktop_shortcuts); l; l = l->next) - _gsignals.Disconnect(l->data, "item-activated"); - - _menu_desktop_shortcuts = dbusmenu_menuitem_new(); - dbusmenu_menuitem_set_root(_menu_desktop_shortcuts, TRUE); - - _desktop_shortcuts = indicator_desktop_shortcuts_new(desktop_file.c_str(), "Unity"); - const gchar** nicks = indicator_desktop_shortcuts_get_nicks(_desktop_shortcuts); + for (GList *l = dbusmenu_menuitem_get_children(_menu_desktop_shortcuts); l; l = l->next) + _gsignals.Disconnect(l->data, "item-activated"); + + _menu_desktop_shortcuts = dbusmenu_menuitem_new(); + dbusmenu_menuitem_set_root(_menu_desktop_shortcuts, TRUE); + + // Build a desktop shortcuts object and tell it that our + // environment is Unity to handle the filtering + _desktop_shortcuts = indicator_desktop_shortcuts_new(desktop_file.c_str(), "Unity"); + // This will get us a list of the nicks available, it should + // always be at least one entry of NULL if there either aren't + // any or they're filtered for the environment we're in + const gchar** nicks = indicator_desktop_shortcuts_get_nicks(_desktop_shortcuts); + + int index = 0; + while (nicks[index]) { + + // Build a dbusmenu item for each nick that is the desktop + // file that is built from it's name and includes a callback + // to the desktop shortcuts object to execute the nick + glib::String name(indicator_desktop_shortcuts_nick_get_name(_desktop_shortcuts, + nicks[index])); + glib::Object<DbusmenuMenuitem> item(dbusmenu_menuitem_new()); + dbusmenu_menuitem_property_set(item, DBUSMENU_MENUITEM_PROP_LABEL, name); + dbusmenu_menuitem_property_set_bool(item, DBUSMENU_MENUITEM_PROP_ENABLED, TRUE); + dbusmenu_menuitem_property_set_bool(item, DBUSMENU_MENUITEM_PROP_VISIBLE, TRUE); + dbusmenu_menuitem_property_set(item, "shortcut-nick", nicks[index]); + + auto sig = new glib::Signal<void, DbusmenuMenuitem*, gint>(item, "item-activated", + [&] (DbusmenuMenuitem* item, gint) { + const gchar *nick; + nick = dbusmenu_menuitem_property_get(item, "shortcut-nick"); + indicator_desktop_shortcuts_nick_exec(_desktop_shortcuts, nick); + }); + _gsignals.Add(sig); - int index = 0; - while (nicks[index]) - { - glib::String name(indicator_desktop_shortcuts_nick_get_name(_desktop_shortcuts, - nicks[index])); - glib::Object<DbusmenuMenuitem> item(dbusmenu_menuitem_new()); - dbusmenu_menuitem_property_set(item, DBUSMENU_MENUITEM_PROP_LABEL, name); - dbusmenu_menuitem_property_set_bool(item, DBUSMENU_MENUITEM_PROP_ENABLED, TRUE); - dbusmenu_menuitem_property_set_bool(item, DBUSMENU_MENUITEM_PROP_VISIBLE, TRUE); - dbusmenu_menuitem_property_set(item, "shortcut-nick", nicks[index]); - - auto sig = new glib::Signal<void, DbusmenuMenuitem*, gint>(item, "item-activated", - [&] (DbusmenuMenuitem* item, gint) { - const gchar *nick; - nick = dbusmenu_menuitem_property_get(item, "shortcut-nick"); - indicator_desktop_shortcuts_nick_exec(_desktop_shortcuts, nick); - }); - _gsignals.Add(sig); - - dbusmenu_menuitem_child_append(_menu_desktop_shortcuts, item); - index++; - } + dbusmenu_menuitem_child_append(_menu_desktop_shortcuts, item); + index++; } - - g_key_file_free(keyfile); } void BamfLauncherIcon::UpdateMenus() @@ -844,7 +831,7 @@ void BamfLauncherIcon::EnsureMenuItemsReady() _menu_items["Pin"] = glib::Object<DbusmenuMenuitem>(menu_item); } - const char* label = !IsSticky() ? _("Lock to launcher") : _("Unlock from launcher"); + const char* label = !IsSticky() ? _("Lock to Launcher") : _("Unlock from Launcher"); dbusmenu_menuitem_property_set(_menu_items["Pin"], DBUSMENU_MENUITEM_PROP_LABEL, label); @@ -1134,7 +1121,8 @@ bool BamfLauncherIcon::ShowInSwitcher(bool current) if (IsRunning() && IsVisible()) { - if (current) + // If current is true, we only want to show the current workspace. + if (!current) { result = true; } diff --git a/plugins/unityshell/src/DashController.cpp b/plugins/unityshell/src/DashController.cpp index 667825310..762d3cf77 100644 --- a/plugins/unityshell/src/DashController.cpp +++ b/plugins/unityshell/src/DashController.cpp @@ -95,6 +95,7 @@ void Controller::SetupDashView() layout->SetHorizontalExternalMargin(0); window_->SetLayout(layout); + ubus_manager_.UnregisterInterest(UBUS_PLACE_ENTRY_ACTIVATE_REQUEST); } void Controller::SetupRelayoutCallbacks() @@ -278,6 +279,8 @@ void Controller::HideDash(bool restore) window_->EnableInputWindow(false, "Dash", true, false); visible_ = false; + nux::GetWindowCompositor().SetKeyFocusArea(NULL,nux::KEY_NAV_NONE); + if (restore) PluginAdapter::Default ()->restoreInputFocus (); @@ -336,9 +339,7 @@ gboolean Controller::OnViewShowHideFrame(Controller* self) void Controller::OnActivateRequest(GVariant* variant) { EnsureDash(); - ubus_manager_.UnregisterInterest(UBUS_PLACE_ENTRY_ACTIVATE_REQUEST); view_->OnActivateRequest(variant); - ShowDash(); } gboolean Controller::CheckShortcutActivation(const char* key_string) @@ -347,7 +348,9 @@ gboolean Controller::CheckShortcutActivation(const char* key_string) std::string lens_id = view_->GetIdForShortcutActivation(std::string(key_string)); if (lens_id != "") { - OnActivateRequest(g_variant_new("(sus)", lens_id.c_str(), 0, "")); + GVariant* args = g_variant_new("(sus)", lens_id.c_str(), 0, ""); + OnActivateRequest(args); + g_variant_unref(args); return true; } return false; diff --git a/plugins/unityshell/src/DashStyle.cpp b/plugins/unityshell/src/DashStyle.cpp index 6e1d8945c..a982e6ae1 100644 --- a/plugins/unityshell/src/DashStyle.cpp +++ b/plugins/unityshell/src/DashStyle.cpp @@ -110,8 +110,6 @@ public: void SetDefaultValues(); - void Star(cairo_t* cr, double size); - void GetTextExtents(int& width, int& height, int maxWidth, @@ -208,13 +206,14 @@ public: LazyLoadTexture search_magnify_texture_; LazyLoadTexture search_close_texture_; - LazyLoadTexture search_close_glow_texture_; LazyLoadTexture search_spin_texture_; - LazyLoadTexture search_spin_glow_texture_; LazyLoadTexture group_unexpand_texture_; LazyLoadTexture group_expand_texture_; + LazyLoadTexture star_deselected_texture_; + LazyLoadTexture star_selected_texture_; + LazyLoadTexture star_highlight_texture_; }; Style::Impl::Impl(Style* owner) @@ -242,11 +241,12 @@ Style::Impl::Impl(Style* owner) , dash_shine_("/dash_sheen.png") , search_magnify_texture_("/search_magnify.png") , search_close_texture_("/search_close.png") - , search_close_glow_texture_("/search_close_glow.png") , search_spin_texture_("/search_spin.png") - , search_spin_glow_texture_("/search_spin_glow.png") , group_unexpand_texture_("/dash_group_unexpand.png") , group_expand_texture_("/dash_group_expand.png") + , star_deselected_texture_("/star_deselected.png") + , star_selected_texture_("/star_selected.png") + , star_highlight_texture_("/star_highlight.png") { signal_manager_.Add(new glib::Signal<void, GtkSettings*, GParamSpec*> (gtk_settings_get_default(), @@ -669,43 +669,6 @@ void Style::Impl::Blur(cairo_t* cr, int size) cairo_surface_mark_dirty(surface); } -void Style::Impl::Star(cairo_t* cr, double size) -{ - double outter[5][2] = {{0.0, 0.0}, - {0.0, 0.0}, - {0.0, 0.0}, - {0.0, 0.0}, - {0.0, 0.0}}; - double inner[5][2] = {{0.0, 0.0}, - {0.0, 0.0}, - {0.0, 0.0}, - {0.0, 0.0}, - {0.0, 0.0}}; - double angle[5] = {-90.0, -18.0, 54.0, 126.0, 198.0}; - double outterRadius = size; - double innerRadius = size/1.75; - - for (int i = 0; i < 5; i++) - { - outter[i][0] = outterRadius * cos(angle[i] * M_PI / 180.0); - outter[i][1] = outterRadius * sin(angle[i] * M_PI / 180.0); - inner[i][0] = innerRadius * cos((angle[i] + 36.0) * M_PI / 180.0); - inner[i][1] = innerRadius * sin((angle[i] + 36.0) * M_PI / 180.0); - } - - cairo_move_to(cr, outter[0][0], outter[0][1]); - cairo_line_to(cr, inner[0][0], inner[0][1]); - cairo_line_to(cr, outter[1][0], outter[1][1]); - cairo_line_to(cr, inner[1][0], inner[1][1]); - cairo_line_to(cr, outter[2][0], outter[2][1]); - cairo_line_to(cr, inner[2][0], inner[2][1]); - cairo_line_to(cr, outter[3][0], outter[3][1]); - cairo_line_to(cr, inner[3][0], inner[3][1]); - cairo_line_to(cr, outter[4][0], outter[4][1]); - cairo_line_to(cr, inner[4][0], inner[4][1]); - cairo_close_path(cr); -} - void Style::Impl::SetDefaultValues() { // button-label @@ -1786,90 +1749,6 @@ bool Style::ButtonFocusOverlay(cairo_t* cr) return true; } -bool Style::StarEmpty(cairo_t* cr, nux::ButtonVisualState state) -{ - // sanity checks - if (cairo_status(cr) != CAIRO_STATUS_SUCCESS) - return false; - - if (cairo_surface_get_type(cairo_get_target(cr)) != CAIRO_SURFACE_TYPE_IMAGE) - return false; - - double w = cairo_image_surface_get_width(cairo_get_target(cr)); - double h = cairo_image_surface_get_height(cairo_get_target(cr)); - double radius = .85 * h / 2.0; - - cairo_save(cr); - cairo_translate(cr, w / 2.0, h / 2.0); - pimpl->Star(cr, radius); - cairo_set_source_rgba(cr, 1.0, 1.0, 1.0, 0.2); - cairo_fill_preserve(cr); - cairo_set_source_rgba(cr, 1.0, 1.0, 1.0, 0.5); - cairo_set_line_width(cr, 0.75); - cairo_stroke(cr); - cairo_restore(cr); - - return true; -} - -bool Style::StarHalf(cairo_t* cr, nux::ButtonVisualState state) -{ - // sanity checks - if (cairo_status(cr) != CAIRO_STATUS_SUCCESS) - return false; - - if (cairo_surface_get_type(cairo_get_target(cr)) != CAIRO_SURFACE_TYPE_IMAGE) - return false; - - double w = cairo_image_surface_get_width(cairo_get_target(cr)); - double h = cairo_image_surface_get_height(cairo_get_target(cr)); - double radius = .85 * h / 2.0; - - cairo_pattern_t* pattern = NULL; - pattern = cairo_pattern_create_linear(0.0, 0.0, w, 0.0); - cairo_pattern_add_color_stop_rgba(pattern, 0.0, 1.0, 1.0, 1.0, 1.0); - cairo_pattern_add_color_stop_rgba(pattern, .5, 1.0, 1.0, 1.0, 1.0); - cairo_pattern_add_color_stop_rgba(pattern, .5 + 0.01, 1.0, 1.0, 1.0, 0.2); - cairo_pattern_add_color_stop_rgba(pattern, 1.0, 1.0, 1.0, 1.0, 0.2); - cairo_set_source(cr, pattern); - - cairo_save(cr); - cairo_translate(cr, w / 2.0, h / 2.0); - pimpl->Star(cr, radius); - cairo_fill_preserve(cr); - cairo_pattern_destroy(pattern); - cairo_set_source_rgba(cr, 1.0, 1.0, 1.0, 0.5); - cairo_set_line_width(cr, 0.75); - cairo_stroke(cr); - cairo_restore(cr); - - return true; -} - -bool Style::StarFull(cairo_t* cr, nux::ButtonVisualState state) -{ - // sanity checks - if (cairo_status(cr) != CAIRO_STATUS_SUCCESS) - return false; - - if (cairo_surface_get_type(cairo_get_target(cr)) != CAIRO_SURFACE_TYPE_IMAGE) - return false; - - double w = cairo_image_surface_get_width(cairo_get_target(cr)); - double h = cairo_image_surface_get_height(cairo_get_target(cr)); - double radius = .85 * h / 2.0; - - cairo_save(cr); - cairo_translate(cr, w / 2.0, h / 2.0); - pimpl->Star(cr, radius); - cairo_set_source_rgba(cr, 1.0, 1.0, 1.0, 1.0); - cairo_fill_preserve(cr); - cairo_stroke(cr); // to make sure it's as "large" as the empty and half ones - cairo_restore(cr); - - return true; -} - bool Style::MultiRangeSegment(cairo_t* cr, nux::ButtonVisualState state, std::string const& label, @@ -2231,21 +2110,11 @@ nux::BaseTexture* Style::GetSearchCloseIcon() return pimpl->search_close_texture_.texture(); } -nux::BaseTexture* Style::GetSearchCloseGlowIcon() -{ - return pimpl->search_close_glow_texture_.texture(); -} - nux::BaseTexture* Style::GetSearchSpinIcon() { return pimpl->search_spin_texture_.texture(); } -nux::BaseTexture* Style::GetSearchSpinGlowIcon() -{ - return pimpl->search_spin_glow_texture_.texture(); -} - nux::BaseTexture* Style::GetGroupUnexpandIcon() { return pimpl->group_unexpand_texture_.texture(); @@ -2256,6 +2125,21 @@ nux::BaseTexture* Style::GetGroupExpandIcon() return pimpl->group_expand_texture_.texture(); } +nux::BaseTexture* Style::GetStarDeselectedIcon() +{ + return pimpl->star_deselected_texture_.texture(); +} + +nux::BaseTexture* Style::GetStarSelectedIcon() +{ + return pimpl->star_selected_texture_.texture(); +} + +nux::BaseTexture* Style::GetStarHighlightIcon() +{ + return pimpl->star_highlight_texture_.texture(); +} + nux::BaseTexture* Style::GetDashShine() { return pimpl->dash_shine_.texture(); diff --git a/plugins/unityshell/src/DashStyle.h b/plugins/unityshell/src/DashStyle.h index 4807d5b6f..980cd6534 100644 --- a/plugins/unityshell/src/DashStyle.h +++ b/plugins/unityshell/src/DashStyle.h @@ -106,12 +106,6 @@ public: virtual bool ButtonFocusOverlay(cairo_t* cr); - virtual bool StarEmpty(cairo_t* cr, nux::ButtonVisualState state); - - virtual bool StarHalf(cairo_t* cr, nux::ButtonVisualState state); - - virtual bool StarFull(cairo_t* cr, nux::ButtonVisualState state); - virtual bool MultiRangeSegment(cairo_t* cr, nux::ButtonVisualState state, std::string const& label, @@ -185,13 +179,15 @@ public: nux::BaseTexture* GetSearchMagnifyIcon(); nux::BaseTexture* GetSearchCloseIcon(); - nux::BaseTexture* GetSearchCloseGlowIcon(); nux::BaseTexture* GetSearchSpinIcon(); - nux::BaseTexture* GetSearchSpinGlowIcon(); nux::BaseTexture* GetGroupUnexpandIcon(); nux::BaseTexture* GetGroupExpandIcon(); + nux::BaseTexture* GetStarDeselectedIcon(); + nux::BaseTexture* GetStarSelectedIcon(); + nux::BaseTexture* GetStarHighlightIcon(); + sigc::signal<void> changed; private: diff --git a/plugins/unityshell/src/DashView.cpp b/plugins/unityshell/src/DashView.cpp index 0f101e38e..370809114 100644 --- a/plugins/unityshell/src/DashView.cpp +++ b/plugins/unityshell/src/DashView.cpp @@ -37,13 +37,41 @@ namespace unity { namespace dash { - namespace { + nux::logging::Logger logger("unity.dash.view"); } +// This is so we can access some protected members in nux::VLayout and +// break the natural key navigation path. +class DashLayout: public nux::VLayout +{ +public: + DashLayout(NUX_FILE_LINE_DECL) + : nux::VLayout(NUX_FILE_LINE_PARAM) + , area_(nullptr) + {} + + void SetSpecialArea(nux::Area* area) + { + area_ = area; + } + +protected: + nux::Area* KeyNavIteration(nux::KeyNavDirection direction) + { + if (direction == nux::KEY_NAV_DOWN && area_ && area_->HasKeyFocus()) + return nullptr; + else + return nux::VLayout::KeyNavIteration(direction); + } + +private: + nux::Area* area_; +}; + NUX_IMPLEMENT_OBJECT_TYPE(DashView); DashView::DashView() @@ -133,7 +161,7 @@ void DashView::SetupViews() layout_ = new nux::VLayout(); SetLayout(layout_); - content_layout_ = new nux::VLayout(); + content_layout_ = new DashLayout(NUX_TRACKER_LOCATION); content_layout_->SetHorizontalExternalMargin(0); content_layout_->SetVerticalExternalMargin(0); @@ -145,11 +173,12 @@ void DashView::SetupViews() search_bar_->live_search_reached.connect(sigc::mem_fun(this, &DashView::OnLiveSearchReached)); search_bar_->showing_filters.changed.connect([&] (bool showing) { if (active_lens_view_) active_lens_view_->filters_expanded = showing; QueueDraw(); }); content_layout_->AddView(search_bar_, 0, nux::MINOR_POSITION_LEFT); + content_layout_->SetSpecialArea(search_bar_->show_filters()); lenses_layout_ = new nux::VLayout(); content_layout_->AddView(lenses_layout_, 1, nux::MINOR_POSITION_LEFT); - home_view_ = new LensView(home_lens_); + home_view_ = new LensView(home_lens_, nullptr); AddChild(home_view_); active_lens_view_ = home_view_; lens_views_[home_lens_->id] = home_view_; @@ -376,7 +405,7 @@ void DashView::OnLensAdded(Lens::Ptr& lens) std::string id = lens->id; lens_bar_->AddLens(lens); - LensView* view = new LensView(lens); + LensView* view = new LensView(lens, search_bar_->show_filters()); AddChild(view); view->SetVisible(false); view->uri_activated.connect(sigc::mem_fun(this, &DashView::OnUriActivated)); @@ -624,15 +653,20 @@ void DashView::AddProperties(GVariantBuilder* builder) wrapper.add("num-rows", num_rows); } -nux::Area * DashView::KeyNavIteration(nux::KeyNavDirection direction) +nux::Area* DashView::KeyNavIteration(nux::KeyNavDirection direction) { - // We don't want to eat the tab as it's used for IM stuff - if (!search_bar_->im_active()) + if (direction == nux::KEY_NAV_DOWN && search_bar_ && active_lens_view_) { - if (direction == KEY_NAV_TAB_NEXT) - lens_bar_->ActivateNext(); - else if (direction == KEY_NAV_TAB_PREVIOUS) - lens_bar_->ActivatePrevious(); + auto show_filters = search_bar_->show_filters(); + auto fscroll_view = active_lens_view_->fscroll_view(); + + if (show_filters && show_filters->HasKeyFocus()) + { + if (fscroll_view->IsVisible() && fscroll_view) + return fscroll_view->KeyNavIteration(direction); + else + return active_lens_view_->KeyNavIteration(direction); + } } return this; } @@ -649,6 +683,8 @@ Area* DashView::FindKeyFocusArea(unsigned int key_symbol, // Do what nux::View does, but if the event isn't a key navigation, // designate the text entry to process it. + bool ctrl = (special_keys_state & NUX_STATE_CTRL); + nux::KeyNavDirection direction = KEY_NAV_NONE; switch (x11_key_code) { @@ -665,9 +701,11 @@ Area* DashView::FindKeyFocusArea(unsigned int key_symbol, direction = KEY_NAV_RIGHT; break; case NUX_VK_LEFT_TAB: + case NUX_VK_PAGE_UP: direction = KEY_NAV_TAB_PREVIOUS; break; case NUX_VK_TAB: + case NUX_VK_PAGE_DOWN: direction = KEY_NAV_TAB_NEXT; break; case NUX_VK_ENTER: @@ -680,11 +718,87 @@ Area* DashView::FindKeyFocusArea(unsigned int key_symbol, break; } - if (has_key_focus_) + // We should not do it here, but I really don't want to make DashView + // focusable and I'm not able to know if ctrl is pressed in + // DashView::KeyNavIteration. + nux::InputArea* focus_area = nux::GetWindowCompositor().GetKeyFocusArea(); + + if (key_symbol == nux::NUX_KEYDOWN) { - return this; + std::list<nux::Area*> tabs; + for (auto category : active_lens_view_->categories()) + { + if (category->IsVisible()) + tabs.push_back(category); + } + + if (search_bar_ && search_bar_->show_filters() && + search_bar_->show_filters()->IsVisible()) + { + tabs.push_back(search_bar_->show_filters()); + } + + if (active_lens_view_->filter_bar() && active_lens_view_->fscroll_view() && + active_lens_view_->fscroll_view()->IsVisible()) + { + for (auto filter : active_lens_view_->filter_bar()->GetLayout()->GetChildren()) + { + tabs.push_back(filter); + } + } + + if (direction == KEY_NAV_TAB_PREVIOUS) + { + if (ctrl) + { + lens_bar_->ActivatePrevious(); + } + else + { + auto rbegin = tabs.rbegin(); + auto rend = tabs.rend(); + + bool use_the_prev = false; + for (auto tab = rbegin; tab != rend; ++tab) + { + const auto& tab_ptr = *tab; + + if (use_the_prev) + return tab_ptr; + + if (focus_area) + use_the_prev = focus_area->IsChildOf(tab_ptr); + } + + for (auto tab = rbegin; tab != rend; ++tab) + return *tab; + } + } + else if (direction == KEY_NAV_TAB_NEXT) + { + if (ctrl) + { + lens_bar_->ActivateNext(); + } + else + { + bool use_the_next = false; + for (auto tab : tabs) + { + if (use_the_next) + return tab; + + if (focus_area) + use_the_next = focus_area->IsChildOf(tab); + } + + for (auto tab : tabs) + return tab; + } + } } - else if (direction == KEY_NAV_NONE || search_bar_->im_active) + + if (direction == KEY_NAV_NONE || search_bar_->im_active) { // then send the event to the search entry return search_bar_->text_entry(); diff --git a/plugins/unityshell/src/DashView.h b/plugins/unityshell/src/DashView.h index 80b64ada5..9a801f627 100644 --- a/plugins/unityshell/src/DashView.h +++ b/plugins/unityshell/src/DashView.h @@ -42,6 +42,8 @@ namespace unity namespace dash { +class DashLayout; + class DashView : public nux::View, public unity::debug::Introspectable { NUX_DECLARE_OBJECT_TYPE(DashView, nux::View); @@ -116,7 +118,7 @@ private: // View related nux::VLayout* layout_; - nux::VLayout* content_layout_; + DashLayout* content_layout_; SearchBar* search_bar_; nux::VLayout* lenses_layout_; LensBar* lens_bar_; diff --git a/plugins/unityshell/src/DeviceLauncherIcon.cpp b/plugins/unityshell/src/DeviceLauncherIcon.cpp index 260d15c6a..1c646eb33 100644 --- a/plugins/unityshell/src/DeviceLauncherIcon.cpp +++ b/plugins/unityshell/src/DeviceLauncherIcon.cpp @@ -87,13 +87,13 @@ std::list<DbusmenuMenuitem*> DeviceLauncherIcon::GetMenus() DbusmenuMenuitem* menu_item; glib::Object<GDrive> drive(g_volume_get_drive(volume_)); - // "Lock to launcher"/"Unlock from launcher" item + // "Lock to Launcher"/"Unlock from Launcher" item if (DevicesSettings::GetDefault().GetDevicesOption() == DevicesSettings::ONLY_MOUNTED && drive && !g_drive_is_media_removable (drive)) { menu_item = dbusmenu_menuitem_new(); - dbusmenu_menuitem_property_set(menu_item, DBUSMENU_MENUITEM_PROP_LABEL, !keep_in_launcher_ ? _("Lock to launcher") : _("Unlock from launcher")); + dbusmenu_menuitem_property_set(menu_item, DBUSMENU_MENUITEM_PROP_LABEL, !keep_in_launcher_ ? _("Lock to Launcher") : _("Unlock from Launcher")); dbusmenu_menuitem_property_set_bool(menu_item, DBUSMENU_MENUITEM_PROP_ENABLED, true); dbusmenu_menuitem_property_set_bool(menu_item, DBUSMENU_MENUITEM_PROP_VISIBLE, true); diff --git a/plugins/unityshell/src/ElapsedTimeMonitor.cpp b/plugins/unityshell/src/ElapsedTimeMonitor.cpp index 06e78b797..b5afc3aed 100644 --- a/plugins/unityshell/src/ElapsedTimeMonitor.cpp +++ b/plugins/unityshell/src/ElapsedTimeMonitor.cpp @@ -17,6 +17,8 @@ * Authored by: Alex Launi <alex.launi@canonical.com> */ +#include <UnityCore/Variant.h> + #include "ElapsedTimeMonitor.h" #include "TimeUtil.h" @@ -39,7 +41,8 @@ void ElapsedTimeMonitor::StopMonitor(GVariantBuilder* builder) clock_gettime(CLOCK_MONOTONIC, ¤t); int diff = TimeUtil::TimeDelta(¤t, &_start); - g_variant_builder_add(builder, "{sv}", "elapsed-time", g_variant_new_uint32(diff)); + variant::BuilderWrapper(builder) + .add("elapsed-time", diff); } } diff --git a/plugins/unityshell/src/FilterAllButton.h b/plugins/unityshell/src/FilterAllButton.h index 32a8f45cc..d4512663a 100644 --- a/plugins/unityshell/src/FilterAllButton.h +++ b/plugins/unityshell/src/FilterAllButton.h @@ -24,6 +24,7 @@ #define UNITYSHELL_FILTERALLBUTTON_H #include <sigc++/connection.h> +#include <UnityCore/Filter.h> #include "FilterBasicButton.h" diff --git a/plugins/unityshell/src/FilterBar.cpp b/plugins/unityshell/src/FilterBar.cpp index e56c28634..ddd0ae3c5 100644 --- a/plugins/unityshell/src/FilterBar.cpp +++ b/plugins/unityshell/src/FilterBar.cpp @@ -24,6 +24,7 @@ #include <NuxCore/Logger.h> #include "FilterBar.h" +#include "FilterExpanderLabel.h" #include "FilterFactory.h" namespace unity @@ -76,7 +77,8 @@ void FilterBar::AddFilter(Filter::Ptr const& filter) return; } - nux::View* filter_view = factory_.WidgetForFilter(filter); + FilterExpanderLabel* filter_view = factory_.WidgetForFilter(filter); + AddChild(filter_view); filter_map_[filter] = filter_view; GetLayout()->AddView(filter_view, 0, nux::MINOR_POSITION_LEFT, nux::MINOR_SIZE_FULL); } @@ -87,7 +89,8 @@ void FilterBar::RemoveFilter(Filter::Ptr const& filter) { if (iter.first->id == filter->id) { - nux::View* filter_view = iter.second; + FilterExpanderLabel* filter_view = iter.second; + RemoveChild(filter_view); filter_map_.erase(filter_map_.find(iter.first)); GetLayout()->RemoveChildObject(filter_view); break; @@ -112,15 +115,14 @@ void FilterBar::DrawContent(nux::GraphicsEngine& GfxContext, bool force_draw) nux::Color col(0.13f, 0.13f, 0.13f, 0.13f); std::list<Area *>& layout_list = GetLayout()->GetChildren(); - std::list<Area*>::iterator iter; int i = 0; int num_separators = layout_list.size() - 1; - for (iter = layout_list.begin(); iter != layout_list.end(); iter++) + for (auto iter : layout_list) { if (i != num_separators) { - nux::Area* filter_view = (*iter); + nux::Area* filter_view = iter; nux::Geometry const& geom = filter_view->GetGeometry(); unsigned int alpha = 0, src = 0, dest = 0; @@ -134,11 +136,33 @@ void FilterBar::DrawContent(nux::GraphicsEngine& GfxContext, bool force_draw) col); GfxContext.GetRenderStates().SetBlend(alpha, src, dest); } - i++; + ++i; } GfxContext.PopClippingRectangle(); } +bool FilterBar::AcceptKeyNavFocus() +{ + return false; +} + +// +// Introspection +// +std::string FilterBar::GetName() const +{ + return "FilterBar"; +} + +void FilterBar::AddProperties(GVariantBuilder* builder) +{ + variant::BuilderWrapper(builder) + .add("x", GetAbsoluteX()) + .add("y", GetAbsoluteY()) + .add("width", GetAbsoluteWidth()) + .add("height", GetAbsoluteHeight()); +} + } // namespace dash } // namespace unity diff --git a/plugins/unityshell/src/FilterBar.h b/plugins/unityshell/src/FilterBar.h index b35418c32..ed2ec1f72 100644 --- a/plugins/unityshell/src/FilterBar.h +++ b/plugins/unityshell/src/FilterBar.h @@ -28,13 +28,16 @@ #include <UnityCore/Filters.h> #include "FilterFactory.h" +#include "Introspectable.h" namespace unity { namespace dash { -class FilterBar : public nux::View +class FilterExpanderLabel; + +class FilterBar : public nux::View, public debug::Introspectable { NUX_DECLARE_OBJECT_TYPE(FilterBar, nux::View); public: @@ -47,15 +50,20 @@ public: void RemoveFilter(Filter::Ptr const& filter); protected: + virtual bool AcceptKeyNavFocus(); virtual void Draw(nux::GraphicsEngine& GfxContext, bool force_draw); virtual void DrawContent(nux::GraphicsEngine& GfxContext, bool force_draw); + // Introspection + virtual std::string GetName() const; + virtual void AddProperties(GVariantBuilder* builder); + private: void Init(); FilterFactory factory_; Filters::Ptr filters_; - std::map<Filter::Ptr, nux::View*> filter_map_; + std::map<Filter::Ptr, FilterExpanderLabel*> filter_map_; }; } // namespace dash diff --git a/plugins/unityshell/src/FilterBasicButton.cpp b/plugins/unityshell/src/FilterBasicButton.cpp index aef95a990..1258d169d 100644 --- a/plugins/unityshell/src/FilterBasicButton.cpp +++ b/plugins/unityshell/src/FilterBasicButton.cpp @@ -68,6 +68,18 @@ void FilterBasicButton::Init() InitTheme(); SetAcceptKeyNavFocusOnMouseDown(false); + SetAcceptKeyNavFocusOnMouseEnter(true); + + key_nav_focus_change.connect([&] (nux::Area*, bool, nux::KeyNavDirection) + { + QueueDraw(); + }); + + key_nav_focus_activate.connect([&](nux::Area*) + { + if (GetInputEventSensitivity()) + Active() ? Deactivate() : Activate(); + }); } void FilterBasicButton::InitTheme() diff --git a/plugins/unityshell/src/FilterBasicButton.h b/plugins/unityshell/src/FilterBasicButton.h index 7ee115b7f..8216938f2 100644 --- a/plugins/unityshell/src/FilterBasicButton.h +++ b/plugins/unityshell/src/FilterBasicButton.h @@ -26,8 +26,6 @@ #include <Nux/CairoWrapper.h> #include <Nux/ToggleButton.h> -#include "FilterWidget.h" - namespace unity { namespace dash diff --git a/plugins/unityshell/src/FilterExpanderLabel.cpp b/plugins/unityshell/src/FilterExpanderLabel.cpp index 68e325c2d..6d256ba38 100644 --- a/plugins/unityshell/src/FilterExpanderLabel.cpp +++ b/plugins/unityshell/src/FilterExpanderLabel.cpp @@ -21,7 +21,6 @@ */ #include "DashStyle.h" -#include "FilterBasicButton.h" #include "FilterExpanderLabel.h" namespace @@ -54,12 +53,13 @@ public: ExpanderView(NUX_FILE_LINE_DECL) : nux::View(NUX_FILE_LINE_PARAM) { + SetAcceptKeyNavFocusOnMouseDown(false); + SetAcceptKeyNavFocusOnMouseEnter(true); } protected: void Draw(nux::GraphicsEngine& graphics_engine, bool force_draw) - { - }; + {}; void DrawContent(nux::GraphicsEngine& graphics_engine, bool force_draw) { @@ -69,7 +69,17 @@ protected: bool AcceptKeyNavFocus() { - return false; + return true; + } + + nux::Area* FindAreaUnderMouse(const nux::Point& mouse_position, nux::NuxEventType event_type) + { + bool mouse_inside = TestMousePointerInclusionFilterMouseWheel(mouse_position, event_type); + + if (mouse_inside == false) + return nullptr; + + return this; } }; @@ -83,7 +93,7 @@ namespace dash NUX_IMPLEMENT_OBJECT_TYPE(FilterExpanderLabel); FilterExpanderLabel::FilterExpanderLabel(std::string const& label, NUX_FILE_LINE_DECL) - : FilterWidget(NUX_FILE_LINE_PARAM) + : nux::View(NUX_FILE_LINE_PARAM) , expanded(true) , layout_(nullptr) , top_bar_layout_(nullptr) @@ -96,6 +106,8 @@ FilterExpanderLabel::FilterExpanderLabel(std::string const& label, NUX_FILE_LINE { expanded.changed.connect(sigc::mem_fun(this, &FilterExpanderLabel::DoExpandChange)); BuildLayout(); + + SetAcceptKeyNavFocusOnMouseDown(false); } FilterExpanderLabel::~FilterExpanderLabel() @@ -179,26 +191,32 @@ void FilterExpanderLabel::BuildLayout() SetLayout(layout_); // Lambda functions - auto mouse_redraw = [&](int x, int y, unsigned long b, unsigned long k) + auto mouse_expand = [&](int x, int y, unsigned long b, unsigned long k) + { + expanded = !expanded; + }; + + auto key_redraw = [&](nux::Area*, bool, nux::KeyNavDirection) { QueueDraw(); }; - auto mouse_expand = [&](int x, int y, unsigned long b, unsigned long k) + auto key_expand = [&](nux::Area*) { expanded = !expanded; }; // Signals expander_view_->mouse_click.connect(mouse_expand); - expander_view_->mouse_enter.connect(mouse_redraw); - expander_view_->mouse_leave.connect(mouse_redraw); + expander_view_->key_nav_focus_change.connect(key_redraw); + expander_view_->key_nav_focus_activate.connect(key_expand); cairo_label_->mouse_click.connect(mouse_expand); - cairo_label_->mouse_enter.connect(mouse_redraw); - cairo_label_->mouse_leave.connect(mouse_redraw); expand_icon_->mouse_click.connect(mouse_expand); - expand_icon_->mouse_enter.connect(mouse_redraw); - expand_icon_->mouse_leave.connect(mouse_redraw); + key_nav_focus_change.connect([&](nux::Area* area, bool has_focus, nux::KeyNavDirection direction) + { + if(has_focus) + nux::GetWindowCompositor().SetKeyFocusArea(expander_view_); + }); QueueRelayout(); NeedRedraw(); @@ -229,9 +247,7 @@ void FilterExpanderLabel::DoExpandChange(bool change) bool FilterExpanderLabel::ShouldBeHighlighted() { - return ((expander_view_ && expander_view_->IsMouseInside()) || - (cairo_label_ && cairo_label_->IsMouseInside()) || - (expand_icon_ && expand_icon_->IsMouseInside())); + return ((expander_view_ && expander_view_->HasKeyFocus())); } void FilterExpanderLabel::Draw(nux::GraphicsEngine& GfxContext, bool force_draw) @@ -271,5 +287,28 @@ void FilterExpanderLabel::DrawContent(nux::GraphicsEngine& GfxContext, bool forc GfxContext.PopClippingRectangle(); } +// +// Key navigation +// +bool FilterExpanderLabel::AcceptKeyNavFocus() +{ + return true; +} + +// +// Introspection +// +std::string FilterExpanderLabel::GetName() const +{ + return "FilterExpanderLabel"; +} + +void FilterExpanderLabel::AddProperties(GVariantBuilder* builder) +{ + unity::variant::BuilderWrapper wrapper(builder); + + wrapper.add("expander-has-focus", expander_view_->HasKeyFocus()); +} + } // namespace dash } // namespace unity diff --git a/plugins/unityshell/src/FilterExpanderLabel.h b/plugins/unityshell/src/FilterExpanderLabel.h index 5acc1cc2b..9d4346c51 100644 --- a/plugins/unityshell/src/FilterExpanderLabel.h +++ b/plugins/unityshell/src/FilterExpanderLabel.h @@ -28,11 +28,13 @@ #include <Nux/Nux.h> #include <Nux/GridHLayout.h> #include <Nux/HLayout.h> +#include <Nux/View.h> #include <Nux/VLayout.h> #include <Nux/StaticText.h> +#include <UnityCore/Filter.h> -#include "FilterWidget.h" #include "IconTexture.h" +#include "Introspectable.h" namespace nux { @@ -44,9 +46,9 @@ namespace unity namespace dash { -class FilterExpanderLabel : public FilterWidget +class FilterExpanderLabel : public nux::View, public debug::Introspectable { - NUX_DECLARE_OBJECT_TYPE(FilterExpanderLabel, FilterWidget); + NUX_DECLARE_OBJECT_TYPE(FilterExpanderLabel, nux::View); public: FilterExpanderLabel(std::string const& label, NUX_FILE_LINE_PROTO); virtual ~FilterExpanderLabel(); @@ -55,12 +57,20 @@ public: void SetLabel(std::string const& label); void SetContents(nux::Layout* layout); + virtual void SetFilter(Filter::Ptr const& filter) = 0; + virtual std::string GetFilterType() = 0; + nux::Property<bool> expanded; protected: + virtual bool AcceptKeyNavFocus(); virtual void Draw(nux::GraphicsEngine& GfxContext, bool force_draw); virtual void DrawContent(nux::GraphicsEngine& GfxContext, bool force_draw); + // Introspection + virtual std::string GetName() const; + virtual void AddProperties(GVariantBuilder* builder); + private: void BuildLayout(); void DoExpandChange(bool change); diff --git a/plugins/unityshell/src/FilterFactory.cpp b/plugins/unityshell/src/FilterFactory.cpp index b6451aed4..d5471a022 100644 --- a/plugins/unityshell/src/FilterFactory.cpp +++ b/plugins/unityshell/src/FilterFactory.cpp @@ -44,12 +44,12 @@ namespace unity namespace dash { -nux::View* FilterFactory::WidgetForFilter(Filter::Ptr const& filter) +FilterExpanderLabel* FilterFactory::WidgetForFilter(Filter::Ptr const& filter) { std::string filter_type(filter->renderer_name); LOG_DEBUG(logger) << "building filter of type, " << filter_type; - FilterWidget* widget = nullptr; + FilterExpanderLabel* widget = nullptr; if (filter_type == renderer_type_check_options) { widget = new FilterGenre(2, NUX_TRACKER_LOCATION); diff --git a/plugins/unityshell/src/FilterFactory.h b/plugins/unityshell/src/FilterFactory.h index 59ff4f0eb..a22d7c6a9 100644 --- a/plugins/unityshell/src/FilterFactory.h +++ b/plugins/unityshell/src/FilterFactory.h @@ -30,10 +30,12 @@ namespace unity namespace dash { +class FilterExpanderLabel; + class FilterFactory { public: - nux::View* WidgetForFilter(Filter::Ptr const& filter); + FilterExpanderLabel* WidgetForFilter(Filter::Ptr const& filter); }; } // namespace dash diff --git a/plugins/unityshell/src/FilterGenreButton.h b/plugins/unityshell/src/FilterGenreButton.h index 693e0cf06..b48b3277b 100644 --- a/plugins/unityshell/src/FilterGenreButton.h +++ b/plugins/unityshell/src/FilterGenreButton.h @@ -24,8 +24,8 @@ #include <Nux/Nux.h> #include <Nux/ToggleButton.h> +#include <UnityCore/Filter.h> -#include "FilterWidget.h" #include "FilterBasicButton.h" namespace unity diff --git a/plugins/unityshell/src/FilterMultiRangeButton.cpp b/plugins/unityshell/src/FilterMultiRangeButton.cpp index 63e7ec6aa..112b06977 100644 --- a/plugins/unityshell/src/FilterMultiRangeButton.cpp +++ b/plugins/unityshell/src/FilterMultiRangeButton.cpp @@ -54,9 +54,11 @@ void FilterMultiRangeButton::Init() { InitTheme(); SetAcceptKeyNavFocusOnMouseDown(false); + SetAcceptKeyNavFocusOnMouseEnter(true); state_change.connect(sigc::mem_fun(this, &FilterMultiRangeButton::OnActivated)); key_nav_focus_change.connect([&](nux::Area*, bool, nux::KeyNavDirection) { QueueDraw(); }); + key_nav_focus_activate.connect([&](nux::Area* area) { Active() ? Deactivate() : Activate(); }); } void FilterMultiRangeButton::OnActivated(nux::Area* area) @@ -237,7 +239,6 @@ void FilterMultiRangeButton::Draw(nux::GraphicsEngine& GfxContext, bool force_dr col); nux::BaseTexture* texture = normal_[MapKey(has_arrow_, side_)]->GetTexture(); - //FIXME - dashstyle does not give us a focused state yet, so ignore if (GetVisualState() == nux::ButtonVisualState::VISUAL_STATE_PRELIGHT) { texture = prelight_[MapKey(has_arrow_, side_)]->GetTexture(); diff --git a/plugins/unityshell/src/FilterMultiRangeButton.h b/plugins/unityshell/src/FilterMultiRangeButton.h index 90dc65f58..2dd52466e 100644 --- a/plugins/unityshell/src/FilterMultiRangeButton.h +++ b/plugins/unityshell/src/FilterMultiRangeButton.h @@ -28,8 +28,7 @@ #include <Nux/CairoWrapper.h> #include <Nux/ToggleButton.h> #include <Nux/View.h> - -#include "FilterWidget.h" +#include <UnityCore/Filter.h> namespace unity { diff --git a/plugins/unityshell/src/FilterMultiRangeWidget.cpp b/plugins/unityshell/src/FilterMultiRangeWidget.cpp index 18fc9452a..7b0b750f2 100644 --- a/plugins/unityshell/src/FilterMultiRangeWidget.cpp +++ b/plugins/unityshell/src/FilterMultiRangeWidget.cpp @@ -113,7 +113,7 @@ void FilterMultiRange::OnActiveChanged(bool value) button->SetHasArrow(MultiRangeArrow::BOTH); else if (index == start) button->SetHasArrow(MultiRangeArrow::LEFT); - else if (index == end) + else if (index == end && index != 0) button->SetHasArrow(MultiRangeArrow::RIGHT); else button->SetHasArrow(MultiRangeArrow::NONE); diff --git a/plugins/unityshell/src/FilterRatingsButton.cpp b/plugins/unityshell/src/FilterRatingsButton.cpp index 9b1cb65ca..4a862c8cc 100644 --- a/plugins/unityshell/src/FilterRatingsButton.cpp +++ b/plugins/unityshell/src/FilterRatingsButton.cpp @@ -27,19 +27,39 @@ #include "DashStyle.h" #include "FilterRatingsButton.h" +namespace +{ +const int star_size = 28; +const int star_gap = 10; +const int num_stars = 5; +} + namespace unity { namespace dash { - FilterRatingsButton::FilterRatingsButton(NUX_FILE_LINE_DECL) : nux::ToggleButton(NUX_FILE_LINE_PARAM) + , focused_star_(-1) { - InitTheme(); SetAcceptKeyNavFocusOnMouseDown(false); + SetAcceptKeyNavFocusOnMouseEnter(true); mouse_up.connect(sigc::mem_fun(this, &FilterRatingsButton::RecvMouseUp)); + mouse_move.connect(sigc::mem_fun(this, &FilterRatingsButton::RecvMouseMove)); mouse_drag.connect(sigc::mem_fun(this, &FilterRatingsButton::RecvMouseDrag)); + + key_nav_focus_change.connect([&](nux::Area* area, bool has_focus, nux::KeyNavDirection direction) + { + if (has_focus && direction != nux::KEY_NAV_NONE) + focused_star_ = 0; + else if (!has_focus) + focused_star_ = -1; + + QueueDraw(); + }); + key_nav_focus_activate.connect([&](nux::Area*) { filter_->rating = static_cast<float>(focused_star_+1)/num_stars; }); + key_down.connect(sigc::mem_fun(this, &FilterRatingsButton::OnKeyDown)); } FilterRatingsButton::~FilterRatingsButton() @@ -58,78 +78,11 @@ std::string FilterRatingsButton::GetFilterType() return "FilterRatingsButton"; } -void FilterRatingsButton::InitTheme() -{ - if (!active_empty_) - { - nux::Geometry geometry(GetGeometry()); - geometry.width /= 5; - - active_empty_.reset(new nux::CairoWrapper(geometry, sigc::bind(sigc::mem_fun(this, &FilterRatingsButton::RedrawTheme), 0, nux::ButtonVisualState::VISUAL_STATE_PRESSED))); - normal_empty_.reset(new nux::CairoWrapper(geometry, sigc::bind(sigc::mem_fun(this, &FilterRatingsButton::RedrawTheme), 0, nux::ButtonVisualState::VISUAL_STATE_NORMAL))); - prelight_empty_.reset(new nux::CairoWrapper(geometry, sigc::bind(sigc::mem_fun(this, &FilterRatingsButton::RedrawTheme), 0, nux::ButtonVisualState::VISUAL_STATE_PRELIGHT))); - - active_half_.reset(new nux::CairoWrapper(geometry, sigc::bind(sigc::mem_fun(this, &FilterRatingsButton::RedrawTheme), 1, nux::ButtonVisualState::VISUAL_STATE_PRESSED))); - normal_half_.reset(new nux::CairoWrapper(geometry, sigc::bind(sigc::mem_fun(this, &FilterRatingsButton::RedrawTheme), 1, nux::ButtonVisualState::VISUAL_STATE_NORMAL))); - prelight_half_.reset(new nux::CairoWrapper(geometry, sigc::bind(sigc::mem_fun(this, &FilterRatingsButton::RedrawTheme), 1, nux::ButtonVisualState::VISUAL_STATE_PRELIGHT))); - - active_full_.reset(new nux::CairoWrapper(geometry, sigc::bind(sigc::mem_fun(this, &FilterRatingsButton::RedrawTheme), 2, nux::ButtonVisualState::VISUAL_STATE_PRESSED))); - normal_full_.reset(new nux::CairoWrapper(geometry, sigc::bind(sigc::mem_fun(this, &FilterRatingsButton::RedrawTheme), 2, nux::ButtonVisualState::VISUAL_STATE_NORMAL))); - prelight_full_.reset(new nux::CairoWrapper(geometry, sigc::bind(sigc::mem_fun(this, &FilterRatingsButton::RedrawTheme), 2, nux::ButtonVisualState::VISUAL_STATE_PRELIGHT))); - } -} - -void FilterRatingsButton::RedrawTheme(nux::Geometry const& geom, cairo_t* cr, int type, nux::ButtonVisualState faked_state) -{ - Style& dash_style = Style::Instance(); - if (type == 0) - { - // empty - dash_style.StarEmpty(cr, faked_state); - } - else if (type == 1) - { - // half - dash_style.StarHalf(cr, faked_state); - } - else - { - // full - dash_style.StarFull(cr, faked_state); - } -} - -long FilterRatingsButton::ComputeContentSize() -{ - long ret = nux::Button::ComputeContentSize(); - nux::Geometry geo(GetGeometry()); - - if (cached_geometry_ != geo) - { - geo.width = 27; - active_empty_->Invalidate(geo); - normal_empty_->Invalidate(geo); - prelight_empty_->Invalidate(geo); - - active_half_->Invalidate(geo); - normal_half_->Invalidate(geo); - prelight_half_->Invalidate(geo); - - active_full_->Invalidate(geo); - normal_full_->Invalidate(geo); - prelight_full_->Invalidate(geo); - - cached_geometry_ = geo; - } - - return ret; -} - void FilterRatingsButton::Draw(nux::GraphicsEngine& GfxContext, bool force_draw) { int rating = 0; if (filter_ && filter_->filtering) - rating = static_cast<int>(filter_->rating * 5); + rating = static_cast<int>(filter_->rating * num_stars); // FIXME: 9/26/2011 // We should probably support an API for saying whether the ratings // should or shouldn't support half stars...but our only consumer at @@ -138,11 +91,10 @@ void FilterRatingsButton::Draw(nux::GraphicsEngine& GfxContext, bool force_draw) // int total_half_stars = rating % 2; // int total_full_stars = rating / 2; int total_full_stars = rating; - int total_half_stars = 0; nux::Geometry const& geo = GetGeometry(); nux::Geometry geo_star(geo); - geo_star.width = 27; + geo_star.width = star_size; gPainter.PaintBackground(GfxContext, geo); // set up our texture mode @@ -164,35 +116,27 @@ void FilterRatingsButton::Draw(nux::GraphicsEngine& GfxContext, bool force_draw) geo.height, col); - for (int index = 0; index < 5; index++) + for (int index = 0; index < num_stars; ++index) { - nux::BaseTexture* texture = normal_empty_->GetTexture(); + Style& style = Style::Instance(); + nux::BaseTexture* texture = style.GetStarSelectedIcon(); if (index < total_full_stars) { if (GetVisualState() == nux::ButtonVisualState::VISUAL_STATE_NORMAL) - texture = normal_full_->GetTexture(); + texture = style.GetStarSelectedIcon(); else if (GetVisualState() == nux::ButtonVisualState::VISUAL_STATE_PRELIGHT) - texture = prelight_full_->GetTexture(); + texture = style.GetStarSelectedIcon(); else if (GetVisualState() == nux::ButtonVisualState::VISUAL_STATE_PRESSED) - texture = active_full_->GetTexture(); - } - else if (index < total_full_stars + total_half_stars) - { - if (GetVisualState() == nux::ButtonVisualState::VISUAL_STATE_NORMAL) - texture = normal_half_->GetTexture(); - else if (GetVisualState() == nux::ButtonVisualState::VISUAL_STATE_PRELIGHT) - texture = prelight_half_->GetTexture(); - else if (GetVisualState() == nux::ButtonVisualState::VISUAL_STATE_PRESSED) - texture = active_half_->GetTexture(); + texture = style.GetStarSelectedIcon(); } else { if (GetVisualState() == nux::ButtonVisualState::VISUAL_STATE_NORMAL) - texture = normal_empty_->GetTexture(); + texture = style.GetStarDeselectedIcon(); else if (GetVisualState() == nux::ButtonVisualState::VISUAL_STATE_PRELIGHT) - texture = prelight_empty_->GetTexture(); + texture = style.GetStarDeselectedIcon(); else if (GetVisualState() == nux::ButtonVisualState::VISUAL_STATE_PRESSED) - texture = active_empty_->GetTexture(); + texture = style.GetStarDeselectedIcon(); } GfxContext.QRP_1Tex(geo_star.x, @@ -203,7 +147,18 @@ void FilterRatingsButton::Draw(nux::GraphicsEngine& GfxContext, bool force_draw) texxform, nux::Color(1.0f, 1.0f, 1.0f, 1.0f)); - geo_star.x += geo_star.width + 10; + if (focused_star_ == index) + { + GfxContext.QRP_1Tex(geo_star.x, + geo_star.y, + geo_star.width, + geo_star.height, + style.GetStarHighlightIcon()->GetDeviceTexture(), + texxform, + nux::Color(1.0f, 1.0f, 1.0f, 0.5f)); + } + + geo_star.x += geo_star.width + star_gap; } @@ -216,8 +171,8 @@ static void _UpdateRatingToMouse(RatingsFilter::Ptr filter, int x) int width = 180; float new_rating = (static_cast<float>(x) / width); - // FIXME: change to 10 once we decide to support also half-stars - new_rating = ceil(5 * new_rating) / 5; + // FIXME: change to * 2 once we decide to support also half-stars + new_rating = ceil((num_stars * 1) * new_rating) / (num_stars * 1); new_rating = (new_rating > 1) ? 1 : ((new_rating < 0) ? 0 : new_rating); if (filter) @@ -241,5 +196,71 @@ void FilterRatingsButton::OnRatingsChanged(int rating) NeedRedraw(); } +void FilterRatingsButton::RecvMouseMove(int x, int y, int dx, int dy, + unsigned long button_flags, + unsigned long key_flags) +{ + int width = 180; + focused_star_ = std::max(0, std::min(static_cast<int>(ceil((static_cast<float>(x) / width) * num_stars) - 1), num_stars - 1)); + + if (!HasKeyFocus()) + nux::GetWindowCompositor().SetKeyFocusArea(this); + + QueueDraw(); +} + + +bool FilterRatingsButton::InspectKeyEvent(unsigned int eventType, unsigned int keysym, const char* character) +{ + nux::KeyNavDirection direction = nux::KEY_NAV_NONE; + + switch (keysym) + { + case NUX_VK_LEFT: + direction = nux::KeyNavDirection::KEY_NAV_LEFT; + break; + case NUX_VK_RIGHT: + direction = nux::KeyNavDirection::KEY_NAV_RIGHT; + break; + default: + direction = nux::KeyNavDirection::KEY_NAV_NONE; + break; + } + + if (direction == nux::KeyNavDirection::KEY_NAV_NONE) + return false; + else if (direction == nux::KEY_NAV_LEFT && (focused_star_ <= 0)) + return false; + else if (direction == nux::KEY_NAV_RIGHT && (focused_star_ >= num_stars - 1)) + return false; + else + return true; +} + + +void FilterRatingsButton::OnKeyDown(unsigned long event_type, unsigned long event_keysym, + unsigned long event_state, const TCHAR* character, + unsigned short key_repeat_count) +{ + switch (event_keysym) + { + case NUX_VK_LEFT: + --focused_star_; + break; + case NUX_VK_RIGHT: + ++focused_star_; + break; + default: + return; + } + + QueueDraw(); +} + +bool FilterRatingsButton::AcceptKeyNavFocus() +{ + return true; +} + } // namespace dash } // namespace unity diff --git a/plugins/unityshell/src/FilterRatingsButton.h b/plugins/unityshell/src/FilterRatingsButton.h index b2b90c2b0..91b510d57 100644 --- a/plugins/unityshell/src/FilterRatingsButton.h +++ b/plugins/unityshell/src/FilterRatingsButton.h @@ -45,30 +45,24 @@ public: std::string GetFilterType(); protected: - virtual long ComputeContentSize(); virtual void Draw(nux::GraphicsEngine& GfxContext, bool force_draw); - void InitTheme(); - void RedrawTheme(nux::Geometry const& geom, cairo_t* cr, int type, nux::ButtonVisualState faked_state); + // Key-nav + virtual bool AcceptKeyNavFocus(); + virtual bool InspectKeyEvent(unsigned int eventType, unsigned int keysym, const char* character); + +private: + void OnKeyDown(unsigned long event_type, unsigned long event_keysym, + unsigned long event_state, const TCHAR* character, + unsigned short key_repeat_count); void RecvMouseUp(int x, int y, unsigned long button_flags, unsigned long key_flags); void RecvMouseDrag(int x, int y, int dx, int dy, unsigned long button_flags, unsigned long key_flags); + void RecvMouseMove(int x, int y, int dx, int dy, unsigned long button_flags, unsigned long key_flags); void OnRatingsChanged(int rating); - typedef std::unique_ptr<nux::CairoWrapper> NuxCairoPtr; - - NuxCairoPtr active_empty_; - NuxCairoPtr normal_empty_; - NuxCairoPtr prelight_empty_; - NuxCairoPtr active_half_; - NuxCairoPtr normal_half_; - NuxCairoPtr prelight_half_; - NuxCairoPtr active_full_; - NuxCairoPtr normal_full_; - NuxCairoPtr prelight_full_; - nux::Geometry cached_geometry_; - dash::RatingsFilter::Ptr filter_; + int focused_star_; }; diff --git a/plugins/unityshell/src/FilterRatingsWidget.cpp b/plugins/unityshell/src/FilterRatingsWidget.cpp index 9c9749548..b09931518 100644 --- a/plugins/unityshell/src/FilterRatingsWidget.cpp +++ b/plugins/unityshell/src/FilterRatingsWidget.cpp @@ -30,6 +30,13 @@ #include "FilterRatingsButton.h" #include "FilterRatingsWidget.h" +namespace +{ +const int top_padding = 11; +const int bottom_padding = 12; +const int star_size = 28; +} + namespace unity { namespace dash @@ -43,8 +50,9 @@ FilterRatingsWidget::FilterRatingsWidget(NUX_FILE_LINE_DECL) all_button_ = new FilterAllButton(NUX_TRACKER_LOCATION); nux::VLayout* layout = new nux::VLayout(NUX_TRACKER_LOCATION); - layout->SetTopAndBottomPadding(11, 12); + layout->SetTopAndBottomPadding(top_padding, bottom_padding); ratings_ = new FilterRatingsButton(NUX_TRACKER_LOCATION); + ratings_->SetMinimumHeight(star_size); layout->AddView(ratings_); diff --git a/plugins/unityshell/src/FilterWidget.cpp b/plugins/unityshell/src/FilterWidget.cpp deleted file mode 100644 index 655037421..000000000 --- a/plugins/unityshell/src/FilterWidget.cpp +++ /dev/null @@ -1,39 +0,0 @@ -// -*- Mode: C++; indent-tabs-mode: nil; tab-width: 2 -*- -/* - * Copyright 2011 Canonical Ltd. - * - * This program is free software: you can redistribute it and/or modify it - * under the terms of the GNU Lesser General Public License version 3, as - * published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranties of - * MERCHANTABILITY, SATISFACTORY QUALITY or FITNESS FOR A PARTICULAR - * PURPOSE. See the applicable version of the GNU Lesser General Public - * License for more details. - * - * You should have received a copy of both the GNU Lesser General Public - * License version 3 along with this program. If not, see - * <http://www.gnu.org/licenses/> - * - * Authored by: Andrea Azzarone <azzaronea@gmail.com> - * - */ - -#include "FilterWidget.h" - -namespace unity -{ -namespace dash -{ - -NUX_IMPLEMENT_OBJECT_TYPE(FilterWidget); - -FilterWidget::FilterWidget( NUX_FILE_LINE_DECL) - : nux::View(NUX_FILE_LINE_PARAM) -{ - SetAcceptKeyNavFocusOnMouseDown(false); -} - -} // namespace dash -} // namespace unity diff --git a/plugins/unityshell/src/FilterWidget.h b/plugins/unityshell/src/FilterWidget.h deleted file mode 100644 index 2c9e323e1..000000000 --- a/plugins/unityshell/src/FilterWidget.h +++ /dev/null @@ -1,48 +0,0 @@ -// -*- Mode: C++; indent-tabs-mode: nil; tab-width: 2 -*- -/* - * Copyright 2011 Canonical Ltd. - * - * This program is free software: you can redistribute it and/or modify it - * under the terms of the GNU Lesser General Public License version 3, as - * published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranties of - * MERCHANTABILITY, SATISFACTORY QUALITY or FITNESS FOR A PARTICULAR - * PURPOSE. See the applicable version of the GNU Lesser General Public - * License for more details. - * - * You should have received a copy of both the GNU Lesser General Public - * License version 3 along with this program. If not, see - * <http://www.gnu.org/licenses/> - * - * Authored by: Gordon Allott <gord.allott@canonical.com> - * - */ -#ifndef UNITYSHELL_FILTERWIDGET_H -#define UNITYSHELL_FILTERWIDGET_H - -#include <Nux/Nux.h> -#include <Nux/View.h> -#include <UnityCore/Filter.h> - -namespace unity -{ -namespace dash -{ - -class FilterWidget : public nux::View -{ - NUX_DECLARE_OBJECT_TYPE(FilterWidget, nux::View); -public: - FilterWidget(NUX_FILE_LINE_PROTO); - virtual ~FilterWidget() {}; - - virtual void SetFilter(Filter::Ptr const& filter) = 0; - virtual std::string GetFilterType() = 0; -}; - -} // namespace dash -} // namespace unity - -#endif //UNITYSHELL_FILTERWIDGET_H diff --git a/plugins/unityshell/src/GestureEngine.cpp b/plugins/unityshell/src/GestureEngine.cpp index 96110111f..5a72843cb 100644 --- a/plugins/unityshell/src/GestureEngine.cpp +++ b/plugins/unityshell/src/GestureEngine.cpp @@ -85,13 +85,18 @@ GestureEngine::FindCompWindow(Window window) Window parent, root; Window* children = NULL; unsigned int nchildren; + Status status; - XQueryTree(_screen->dpy(), window, &root, &parent, &children, &nchildren); + status = XQueryTree(_screen->dpy(), window, &root, &parent, &children, &nchildren); + if (status == 0) + break; if (children) XFree(children); - if (parent == root) + // parent will be zero when the window passed to this method is already the + // root one. + if (parent == root || parent == 0) break; window = parent; @@ -215,7 +220,7 @@ GestureEngine::OnRotateFinish(GeisAdapter::GeisRotateData* data) void GestureEngine::OnTouchStart(GeisAdapter::GeisTouchData* data) { - if (data->touches == 3) + if (data->touches == 3 && data->window != 0) { CompWindow* result = FindCompWindow(data->window); diff --git a/plugins/unityshell/src/HudButton.cpp b/plugins/unityshell/src/HudButton.cpp index 380753cb7..704c9405f 100644 --- a/plugins/unityshell/src/HudButton.cpp +++ b/plugins/unityshell/src/HudButton.cpp @@ -30,6 +30,8 @@ #include <NuxImage/CairoGraphics.h> #include <NuxGraphics/NuxGraphics.h> #include <UnityCore/GLibWrapper.h> +#include <UnityCore/Variant.h> + #include "DashStyle.h" #include "HudButton.h" @@ -39,9 +41,9 @@ namespace nux::logging::Logger logger("unity.hud.HudButton"); } -namespace unity +namespace unity { -namespace hud +namespace hud { @@ -83,9 +85,9 @@ HudButton::~HudButton() { void HudButton::Init() { InitTheme(); - key_nav_focus_change.connect([this](nux::Area *area, bool recieving, nux::KeyNavDirection direction) - { - QueueDraw(); + key_nav_focus_change.connect([this](nux::Area *area, bool recieving, nux::KeyNavDirection direction) + { + QueueDraw(); }); fake_focused.changed.connect([this](bool change) @@ -117,15 +119,16 @@ void HudButton::InitTheme() void HudButton::RedrawTheme(nux::Geometry const& geom, cairo_t* cr, nux::ButtonVisualState faked_state) { - dash::Style::Instance().SquareButton(cr, faked_state, label_, - is_rounded, 17, + dash::Style::Instance().SquareButton(cr, faked_state, label_, + is_rounded, 17, dash::Alignment::LEFT, true); } bool HudButton::AcceptKeyNavFocus() { - // say we can't be focused if we have fake focus on - return !fake_focused; + // The button will not receive the keyboard focus. The keyboard focus is always to remain with the + // text entry in the hud. + return false; } @@ -142,11 +145,11 @@ long HudButton::ComputeContentSize () cached_geometry_ = geo; } - + return ret; -} +} -void HudButton::Draw(nux::GraphicsEngine& GfxContext, bool force_draw) +void HudButton::Draw(nux::GraphicsEngine& GfxContext, bool force_draw) { nux::Geometry const& geo = GetGeometry(); gPainter.PaintBackground(GfxContext, geo); @@ -160,7 +163,7 @@ void HudButton::Draw(nux::GraphicsEngine& GfxContext, bool force_draw) GfxContext.GetRenderStates().GetBlend(alpha, src, dest); GfxContext.GetRenderStates().SetPremultipliedBlend(nux::SRC_OVER); GfxContext.GetRenderStates().SetBlend(true); - + nux::Color col = nux::color::Black; col.alpha = 0; GfxContext.QRP_Color(geo.x, @@ -215,7 +218,8 @@ std::string HudButton::GetName() const void HudButton::AddProperties(GVariantBuilder* builder) { - g_variant_builder_add(builder, "{sv}", "label", g_variant_new_string(label_.c_str())); + variant::BuilderWrapper(builder) + .add("label", label_); } } diff --git a/plugins/unityshell/src/HudController.cpp b/plugins/unityshell/src/HudController.cpp index a05daeedd..3094dda6b 100644 --- a/plugins/unityshell/src/HudController.cpp +++ b/plugins/unityshell/src/HudController.cpp @@ -20,6 +20,7 @@ #include <NuxCore/Logger.h> #include <Nux/HLayout.h> +#include <UnityCore/Variant.h> #include "PluginAdapter.h" #include "PanelStyle.h" #include "UBusMessages.h" @@ -40,12 +41,13 @@ nux::logging::Logger logger("unity.hud.controller"); Controller::Controller() : launcher_width(66) , hud_service_("com.canonical.hud", "/com/canonical/hud") - , window_(0) + , window_(nullptr) , visible_(false) , need_show_(false) , timeline_id_(0) , last_opacity_(0.0f) , start_time_(0) + , view_(nullptr) { LOG_DEBUG(logger) << "hud startup"; SetupRelayoutCallbacks(); @@ -110,6 +112,8 @@ void Controller::SetupHudView() view_->search_activated.connect(sigc::mem_fun(this, &Controller::OnSearchActivated)); view_->query_activated.connect(sigc::mem_fun(this, &Controller::OnQueryActivated)); view_->query_selected.connect(sigc::mem_fun(this, &Controller::OnQuerySelected)); + // Add to the debug introspection. + AddChild(view_); } void Controller::SetupRelayoutCallbacks() @@ -186,7 +190,7 @@ void Controller::OnScreenUngrabbed() { LOG_DEBUG(logger) << "OnScreenUngrabbed called"; if (need_show_) - { + { nux::GetWindowCompositor().SetKeyFocusArea(view_->default_focus()); window_->PushToFront(); @@ -225,10 +229,10 @@ void Controller::ShowHud() PluginAdapter* adaptor = PluginAdapter::Default(); LOG_DEBUG(logger) << "Showing the hud"; EnsureHud(); - + if (visible_ || adaptor->IsExpoActive() || adaptor->IsScaleActive()) return; - + if (adaptor->IsScreenGrabbed()) { need_show_ = true; @@ -267,7 +271,7 @@ void Controller::ShowHud() GVariant* info = g_variant_new(UBUS_OVERLAY_FORMAT_STRING, "hud", FALSE, UScreen::GetDefault()->GetMonitorWithMouse()); ubus.SendMessage(UBUS_OVERLAY_SHOWN, info); - + nux::GetWindowCompositor().SetKeyFocusArea(view_->default_focus()); window_->SetEnterFocusInputArea(view_->default_focus()); } @@ -342,15 +346,18 @@ gboolean Controller::OnViewShowHideFrame(Controller* self) else { // ensure the text entry is focused - g_timeout_add(500, [] (gpointer data) -> gboolean + g_timeout_add(500, [] (gpointer data) -> gboolean { //THIS IS BAD - VERY VERY BAD LOG_DEBUG(logger) << "Last attempt, forcing window focus"; Controller* self = static_cast<Controller*>(data); - nux::GetWindowCompositor().SetKeyFocusArea(self->view_->default_focus()); - - self->window_->PushToFront(); - self->window_->SetInputFocus(); + if (self->visible_) + { + nux::GetWindowCompositor().SetKeyFocusArea(self->view_->default_focus()); + + self->window_->PushToFront(); + self->window_->SetInputFocus(); + } return FALSE; }, self); } @@ -376,7 +383,7 @@ void Controller::OnSearchActivated(std::string search_string) { unsigned int timestamp = nux::GetWindowThread()->GetGraphicsDisplay().GetCurrentEvent().x11_timestamp; hud_service_.ExecuteQueryBySearch(search_string, timestamp); - HideHud(); + ubus.SendMessage(UBUS_HUD_CLOSE_REQUEST); } void Controller::OnQueryActivated(Query::Ptr query) @@ -384,7 +391,7 @@ void Controller::OnQueryActivated(Query::Ptr query) LOG_DEBUG(logger) << "Activating query, " << query->formatted_text; unsigned int timestamp = nux::GetWindowThread()->GetGraphicsDisplay().GetCurrentEvent().x11_timestamp; hud_service_.ExecuteQuery(query, timestamp); - HideHud(); + ubus.SendMessage(UBUS_HUD_CLOSE_REQUEST); } void Controller::OnQuerySelected(Query::Ptr query) @@ -419,7 +426,8 @@ std::string Controller::GetName() const void Controller::AddProperties(GVariantBuilder* builder) { - g_variant_builder_add(builder, "{sv}", "visible", g_variant_new_boolean (visible_)); + variant::BuilderWrapper(builder) + .add("visible", visible_); } diff --git a/plugins/unityshell/src/HudIconTextureSource.cpp b/plugins/unityshell/src/HudIconTextureSource.cpp index 3e9f06736..795ddf9d9 100644 --- a/plugins/unityshell/src/HudIconTextureSource.cpp +++ b/plugins/unityshell/src/HudIconTextureSource.cpp @@ -26,6 +26,10 @@ #include <Nux/Nux.h> #include <NuxCore/Logger.h> +namespace +{ + nux::logging::Logger logger("unity.hud.HudIconTextureSource"); +} namespace unity { @@ -44,46 +48,54 @@ HudIconTextureSource::~HudIconTextureSource() void HudIconTextureSource::ColorForIcon(GdkPixbuf* pixbuf) { - unsigned int width = gdk_pixbuf_get_width(pixbuf); - unsigned int height = gdk_pixbuf_get_height(pixbuf); - unsigned int row_bytes = gdk_pixbuf_get_rowstride(pixbuf); - - long int rtotal = 0, gtotal = 0, btotal = 0; - float total = 0.0f; - - guchar* img = gdk_pixbuf_get_pixels(pixbuf); - - for (unsigned int i = 0; i < width; i++) + if (GDK_IS_PIXBUF(pixbuf)) { - for (unsigned int j = 0; j < height; j++) + unsigned int width = gdk_pixbuf_get_width(pixbuf); + unsigned int height = gdk_pixbuf_get_height(pixbuf); + unsigned int row_bytes = gdk_pixbuf_get_rowstride(pixbuf); + + long int rtotal = 0, gtotal = 0, btotal = 0; + float total = 0.0f; + + guchar* img = gdk_pixbuf_get_pixels(pixbuf); + + for (unsigned int i = 0; i < width; i++) { - guchar* pixels = img + (j * row_bytes + i * 4); - guchar r = *(pixels + 0); - guchar g = *(pixels + 1); - guchar b = *(pixels + 2); - guchar a = *(pixels + 3); - - float saturation = (MAX(r, MAX(g, b)) - MIN(r, MIN(g, b))) / 255.0f; - float relevance = .1 + .9 * (a / 255.0f) * saturation; - - rtotal += (guchar)(r * relevance); - gtotal += (guchar)(g * relevance); - btotal += (guchar)(b * relevance); - - total += relevance * 255; + for (unsigned int j = 0; j < height; j++) + { + guchar* pixels = img + (j * row_bytes + i * 4); + guchar r = *(pixels + 0); + guchar g = *(pixels + 1); + guchar b = *(pixels + 2); + guchar a = *(pixels + 3); + + float saturation = (MAX(r, MAX(g, b)) - MIN(r, MIN(g, b))) / 255.0f; + float relevance = .1 + .9 * (a / 255.0f) * saturation; + + rtotal += (guchar)(r * relevance); + gtotal += (guchar)(g * relevance); + btotal += (guchar)(b * relevance); + + total += relevance * 255; + } } + + nux::color::RedGreenBlue rgb(rtotal / total, + gtotal / total, + btotal / total); + nux::color::HueSaturationValue hsv(rgb); + + if (hsv.saturation > 0.15f) + hsv.saturation = 0.65f; + + hsv.value = 0.90f; + bg_color = nux::Color(nux::color::RedGreenBlue(hsv)); + } + else + { + LOG_ERROR(logger) << "Pixbuf (" << pixbuf << ") passed is non valid"; + bg_color = nux::Color(255,255,255,255); } - - nux::color::RedGreenBlue rgb(rtotal / total, - gtotal / total, - btotal / total); - nux::color::HueSaturationValue hsv(rgb); - - if (hsv.saturation > 0.15f) - hsv.saturation = 0.65f; - - hsv.value = 0.90f; - bg_color = nux::Color(nux::color::RedGreenBlue(hsv)); } nux::Color HudIconTextureSource::BackgroundColor() @@ -107,4 +119,5 @@ nux::BaseTexture* HudIconTextureSource::Emblem() } } -} \ No newline at end of file +} + diff --git a/plugins/unityshell/src/HudView.cpp b/plugins/unityshell/src/HudView.cpp index 40fa518b9..d48e011cc 100644 --- a/plugins/unityshell/src/HudView.cpp +++ b/plugins/unityshell/src/HudView.cpp @@ -30,6 +30,7 @@ #include <NuxCore/Logger.h> #include <UnityCore/GLibWrapper.h> #include <UnityCore/RadioOptionFilter.h> +#include <UnityCore/Variant.h> #include <Nux/HLayout.h> #include <Nux/LayeredLayout.h> @@ -62,7 +63,7 @@ View::View() , last_known_height_(0) , current_height_(0) , timeline_need_more_draw_(false) - + , selected_button_(0) { renderer_.SetOwner(this); renderer_.need_redraw.connect([this] () { @@ -82,31 +83,37 @@ View::View() search_activated.emit(search_bar_->search_string); }); + search_bar_->text_entry()->SetLoseKeyFocusOnKeyNavDirectionUp(false); + search_bar_->text_entry()->SetLoseKeyFocusOnKeyNavDirectionDown(false); + search_bar_->text_entry()->key_nav_focus_change.connect([&](nux::Area *area, bool receiving, nux::KeyNavDirection direction) { - if (buttons_.empty() && !receiving) - { - // we lost focus in the keynav and there are no buttons, we need to steal - // focus back - LOG_ERROR(logger) << "hud search bar lost keynav with no where else to keynav to"; - nux::GetWindowCompositor().SetKeyFocusArea(search_bar_->text_entry()); - } + // We get here when the Hud closes. + // The TextEntry should always have the keyboard focus as long as the hud is open. if (buttons_.empty()) return;// early return on empty button list if (receiving) { - // if the search_bar gets focus, fake focus the first button if it exists if (!buttons_.empty()) { + // If the search_bar gets focus, fake focus the first button if it exists buttons_.back()->fake_focused = true; } } else { - // we are losing focus, so remove the fake focused entry - buttons_.back()->fake_focused = false; + // The hud is closing and there are HudButtons visible. Remove the fake_focus. + // There should be only one HudButton with the fake_focus set to true. + std::list<HudButton::Ptr>::iterator it; + for(it = buttons_.begin(); it != buttons_.end(); ++it) + { + if ((*it)->fake_focused) + { + (*it)->fake_focused = false; + } + } } }); @@ -212,11 +219,12 @@ nux::View* View::default_focus() const void View::SetQueries(Hud::Queries queries) { // remove the previous children - for (auto button = buttons_.begin(); button != buttons_.end(); button++) + for (auto button : buttons_) { - RemoveChild((*button).GetPointer()); + RemoveChild(button.GetPointer()); } - + + selected_button_ = 0; queries_ = queries_; buttons_.clear(); button_views_->Clear(); @@ -226,15 +234,16 @@ void View::SetQueries(Hud::Queries queries) if (found_items > 5) break; - HudButton::Ptr button = HudButton::Ptr(new HudButton()); + HudButton::Ptr button(new HudButton()); buttons_.push_front(button); button->SetQuery(*query); + button_views_->AddView(button.GetPointer(), 0, nux::MINOR_POSITION_LEFT); button->click.connect([&](nux::View* view) { query_activated.emit(dynamic_cast<HudButton*>(view)->GetQuery()); }); - + button->key_nav_focus_activate.connect([&](nux::Area *area) { query_activated.emit(dynamic_cast<HudButton*>(area)->GetQuery()); }); @@ -244,13 +253,16 @@ void View::SetQueries(Hud::Queries queries) query_selected.emit(dynamic_cast<HudButton*>(area)->GetQuery()); }); + // You should never decrement end(). We should fix this loop. button->is_rounded = (query == --(queries.end())) ? true : false; button->fake_focused = (query == (queries.begin())) ? true : false; - + button->SetMinimumWidth(941); found_items++; } - + if (found_items) + selected_button_ = 1; + QueueRelayout(); QueueDraw(); } @@ -450,7 +462,10 @@ std::string View::GetName() const void View::AddProperties(GVariantBuilder* builder) { - + unsigned num_buttons = buttons_.size(); + variant::BuilderWrapper(builder) + .add("selected_button", selected_button_) + .add("num_buttons", num_buttons); } bool View::InspectKeyEvent(unsigned int eventType, @@ -473,13 +488,10 @@ bool View::InspectKeyEvent(unsigned int eventType, return false; } -nux::Area* View::FindKeyFocusArea(unsigned int key_symbol, +nux::Area* View::FindKeyFocusArea(unsigned int event_type, unsigned long x11_key_code, unsigned long special_keys_state) { - // Do what nux::View does, but if the event isn't a key navigation, - // designate the text entry to process it. - nux::KeyNavDirection direction = nux::KEY_NAV_NONE; switch (x11_key_code) { @@ -511,18 +523,118 @@ nux::Area* View::FindKeyFocusArea(unsigned int key_symbol, break; } - if (has_key_focus_) + + if ((event_type == nux::NUX_KEYDOWN) && (x11_key_code == NUX_VK_ESCAPE)) + { + // Escape key! This is how it works: + // -If there is text, clear it and give the focus to the text entry view. + // -If there is no text text, then close the hud. + + if (search_bar_->search_string == "") + { + search_bar_->search_hint = default_text; + ubus.SendMessage(UBUS_HUD_CLOSE_REQUEST); + } + else + { + search_bar_->search_string = ""; + search_bar_->search_hint = default_text; + return search_bar_->text_entry(); + } + return NULL; + } + + if (search_bar_->text_entry()->HasKeyFocus()) { - return this; + if (direction == nux::KEY_NAV_NONE || + direction == nux::KEY_NAV_UP || + direction == nux::KEY_NAV_DOWN || + direction == nux::KEY_NAV_LEFT || + direction == nux::KEY_NAV_RIGHT) + { + // We have received a key character or a keyboard arrow Up or Down (navigation keys). + // Because we have called "SetLoseKeyFocusOnKeyNavDirectionUp(false);" and "SetLoseKeyFocusOnKeyNavDirectionDown(false);" + // on the text entry, the text entry will not loose the keyboard focus. + // All that we need to do here is set the fake_focused value on the HudButton. + + if (!buttons_.empty()) + { + if (event_type == nux::NUX_KEYDOWN && direction == nux::KEY_NAV_UP) + { + std::list<HudButton::Ptr>::iterator it; + for(it = buttons_.begin(); it != buttons_.end(); ++it) + { + if ((*it)->fake_focused) + { + std::list<HudButton::Ptr>::iterator next = it; + ++next; + if (next != buttons_.end()) + { + // The button with the current fake_focus looses it. + (*it)->fake_focused = false; + // The next button gets the fake_focus + (*next)->fake_focused = true; + query_selected.emit((*next)->GetQuery()); + --selected_button_; + } + break; + } + } + } + + if (event_type == nux::NUX_KEYDOWN && direction == nux::KEY_NAV_DOWN) + { + std::list<HudButton::Ptr>::reverse_iterator rit; + for(rit = buttons_.rbegin(); rit != buttons_.rend(); ++rit) + { + if ((*rit)->fake_focused) + { + std::list<HudButton::Ptr>::reverse_iterator next = rit; + ++next; + if(next != buttons_.rend()) + { + // The button with the current fake_focus looses it. + (*rit)->fake_focused = false; + // The next button bellow gets the fake_focus. + (*next)->fake_focused = true; + query_selected.emit((*next)->GetQuery()); + ++selected_button_; + } + break; + } + } + } + } + return search_bar_->text_entry(); + } + + if (direction == nux::KEY_NAV_ENTER) + { + // The "Enter" key has been received and the text entry has the key focus. + // If one of the button has the fake_focus, we get it to emit the query_activated signal. + if (!buttons_.empty()) + { + std::list<HudButton::Ptr>::iterator it; + for(it = buttons_.begin(); it != buttons_.end(); ++it) + { + if ((*it)->fake_focused) + { + query_activated.emit((*it)->GetQuery()); + } + } + } + + // We still choose the text_entry as the receiver of the key focus. + return search_bar_->text_entry(); + } } else if (direction == nux::KEY_NAV_NONE) { - // then send the event to the search entry return search_bar_->text_entry(); } else if (next_object_to_key_focus_area_) { - return next_object_to_key_focus_area_->FindKeyFocusArea(key_symbol, x11_key_code, special_keys_state); + return next_object_to_key_focus_area_->FindKeyFocusArea(event_type, x11_key_code, special_keys_state); } return NULL; } diff --git a/plugins/unityshell/src/HudView.h b/plugins/unityshell/src/HudView.h index 79d87840c..c8fbe9f72 100644 --- a/plugins/unityshell/src/HudView.h +++ b/plugins/unityshell/src/HudView.h @@ -71,7 +71,7 @@ public: sigc::signal<void, Query::Ptr> query_selected; protected: - virtual Area* FindKeyFocusArea(unsigned int key_symbol, + virtual Area* FindKeyFocusArea(unsigned int event_type, unsigned long x11_key_code, unsigned long special_keys_state); @@ -117,6 +117,7 @@ private: int last_known_height_; int current_height_; bool timeline_need_more_draw_; + int selected_button_; }; diff --git a/plugins/unityshell/src/IMTextEntry.cpp b/plugins/unityshell/src/IMTextEntry.cpp index 63bd498c5..e9201497f 100644 --- a/plugins/unityshell/src/IMTextEntry.cpp +++ b/plugins/unityshell/src/IMTextEntry.cpp @@ -21,7 +21,6 @@ #include "IMTextEntry.h" -#include <boost/lexical_cast.hpp> #include <NuxCore/Logger.h> #include <UnityCore/GLibWrapper.h> @@ -36,127 +35,21 @@ nux::logging::Logger logger("unity.imtextentry"); NUX_IMPLEMENT_OBJECT_TYPE(IMTextEntry); IMTextEntry::IMTextEntry() - : TextEntry("", "", 80085) - , preedit_string("") - , im_enabled(false) - , im_active(false) - , focused_(false) +: TextEntry("", NUX_TRACKER_LOCATION) { - g_setenv("IBUS_ENABLE_SYNC_MODE", "1", TRUE); - CheckIMEnabled(); - im_enabled ? SetupMultiIM() : SetupSimpleIM(); - - key_nav_focus_change.connect([&](nux::Area* area, bool focus, nux::KeyNavDirection dir) - { - focus ? OnFocusIn() : OnFocusOut(); - }); mouse_up.connect(sigc::mem_fun(this, &IMTextEntry::OnMouseButtonUp)); } -void IMTextEntry::CheckIMEnabled() -{ - const char* module = g_getenv("GTK_IM_MODULE"); - if (module && - g_strcmp0(module, "") && - g_strcmp0(module, "gtk-im-context-simple")) - im_enabled = true; - - LOG_DEBUG(logger) << "Input method support is " - << (im_enabled ? "enabled" : "disabled"); -} - -void IMTextEntry::SetupSimpleIM() -{ - im_context_ = gtk_im_context_simple_new(); - - sig_manager_.Add(new Signal<void, GtkIMContext*, char*>(im_context_, "commit", sigc::mem_fun(this, &IMTextEntry::OnCommit))); - sig_manager_.Add(new Signal<void, GtkIMContext*>(im_context_, "preedit-changed", sigc::mem_fun(this, &IMTextEntry::OnPreeditChanged))); - sig_manager_.Add(new Signal<void, GtkIMContext*>(im_context_, "preedit-start", sigc::mem_fun(this, &IMTextEntry::OnPreeditStart))); - sig_manager_.Add(new Signal<void, GtkIMContext*>(im_context_, "preedit-end", sigc::mem_fun(this, &IMTextEntry::OnPreeditEnd))); -} - -void IMTextEntry::SetupMultiIM() -{ - im_context_ = gtk_im_multicontext_new(); - - sig_manager_.Add(new Signal<void, GtkIMContext*, char*>(im_context_, "commit", sigc::mem_fun(this, &IMTextEntry::OnCommit))); - sig_manager_.Add(new Signal<void, GtkIMContext*>(im_context_, "preedit-changed", sigc::mem_fun(this, &IMTextEntry::OnPreeditChanged))); - sig_manager_.Add(new Signal<void, GtkIMContext*>(im_context_, "preedit-start", sigc::mem_fun(this, &IMTextEntry::OnPreeditStart))); - sig_manager_.Add(new Signal<void, GtkIMContext*>(im_context_, "preedit-end", sigc::mem_fun(this, &IMTextEntry::OnPreeditEnd))); -} - bool IMTextEntry::InspectKeyEvent(unsigned int event_type, unsigned int keysym, const char* character) { - bool propagate_event = !(TryHandleEvent(event_type, keysym, character)); - - LOG_DEBUG(logger) << "Input method " - << (im_enabled ? gtk_im_multicontext_get_context_id(glib::object_cast<GtkIMMulticontext>(im_context_)) : "simple") - << " " - << (propagate_event ? "did not handle " : "handled ") - << "event (" - << (event_type == NUX_KEYDOWN ? "press" : "release") - << ") "; - - if (propagate_event) - propagate_event = !TryHandleSpecial(event_type, keysym, character); - - if (propagate_event) - { - text_input_mode_ = event_type == NUX_KEYDOWN; - propagate_event = TextEntry::InspectKeyEvent(event_type, keysym, character); - text_input_mode_ = false; - - UpdateCursorLocation(); - } - return propagate_event; -} - -bool IMTextEntry::TryHandleEvent(unsigned int eventType, - unsigned int keysym, - const char* character) -{ - nux::Event event = nux::GetWindowThread()->GetGraphicsDisplay().GetCurrentEvent(); + bool need_to_filter_event = TryHandleSpecial(event_type, keysym, character); + + if (need_to_filter_event) + need_to_filter_event = TextEntry::InspectKeyEvent(event_type, keysym, character); - CheckValidClientWindow(event.x11_window); - - GdkEventKey ev; - KeyEventToGdkEventKey(event, ev); - - return gtk_im_context_filter_keypress(im_context_, &ev); -} - -inline void IMTextEntry::CheckValidClientWindow(Window window) -{ - if (!client_window_) - { - client_window_ = gdk_x11_window_foreign_new_for_display(gdk_display_get_default(), window); - gtk_im_context_set_client_window(im_context_, client_window_); - - if (focused_) - { - gtk_im_context_focus_in(im_context_); - } - } -} - -void IMTextEntry::KeyEventToGdkEventKey(Event& event, GdkEventKey& gdk_event) -{ - gdk_event.type = event.type == nux::NUX_KEYDOWN ? GDK_KEY_PRESS : GDK_KEY_RELEASE; - gdk_event.window = client_window_; - gdk_event.send_event = FALSE; - gdk_event.time = event.x11_timestamp; - gdk_event.state = event.x11_key_state; - gdk_event.keyval = event.x11_keysym; - - gchar* txt = const_cast<gchar*>(event.GetText()); - gdk_event.length = strlen(txt); - gdk_event.string = txt; - - gdk_event.hardware_keycode = event.x11_keycode; - gdk_event.group = 0; - gdk_event.is_modifier = 0; + return need_to_filter_event; } bool IMTextEntry::TryHandleSpecial(unsigned int eventType, unsigned int keysym, const char* character) @@ -166,13 +59,14 @@ bool IMTextEntry::TryHandleSpecial(unsigned int eventType, unsigned int keysym, bool shift = (event.GetKeyState() & NUX_STATE_SHIFT); bool ctrl = (event.GetKeyState() & NUX_STATE_CTRL); + /* If there is preedit, handle the event else where, but we + want to be able to copy/paste while ibus is active */ + if (!preedit_.empty()) + return true; + if (eventType != NUX_KEYDOWN) return false; - /* If IM is active, de-activate Copy & Paste */ - if (im_active) - return true; - if (((keyval == NUX_VK_x) && ctrl && !shift) || ((keyval == NUX_VK_DELETE) && shift && !ctrl)) { @@ -194,15 +88,16 @@ bool IMTextEntry::TryHandleSpecial(unsigned int eventType, unsigned int keysym, } else { - return false; + return true; } - return true; + return false; } void IMTextEntry::Cut() { Copy(); DeleteSelection(); + QueueRefresh (true, true); } void IMTextEntry::Copy() @@ -210,8 +105,7 @@ void IMTextEntry::Copy() int start=0, end=0; if (GetSelectionBounds(&start, &end)) { - GtkClipboard* clip = gtk_clipboard_get_for_display(gdk_display_get_default(), - GDK_SELECTION_CLIPBOARD); + GtkClipboard* clip = gtk_clipboard_get(GDK_SELECTION_CLIPBOARD); gtk_clipboard_set_text(clip, text_.c_str() + start, end - start); } } @@ -219,124 +113,42 @@ void IMTextEntry::Copy() void IMTextEntry::Paste(bool primary) { GdkAtom origin = primary ? GDK_SELECTION_PRIMARY : GDK_SELECTION_CLIPBOARD; - glib::Object<GtkClipboard> clip(gtk_clipboard_get_for_display(gdk_display_get_default(), - origin)); + GtkClipboard* clip = gtk_clipboard_get(origin); + auto callback = [](GtkClipboard* clip, const char* text, gpointer user_data) { IMTextEntry* self = static_cast<IMTextEntry*>(user_data); if (text) - self->InsertTextAt(self->cursor_, std::string(text)); + self->InsertText(std::string(text)); }; gtk_clipboard_request_text(clip, callback, this); } -void IMTextEntry::InsertTextAt(unsigned int position, std::string const& text) +void IMTextEntry::InsertText(std::string const& text) { DeleteSelection(); if (!text.empty()) { std::string new_text(GetText()); - new_text.insert(position, text); + new_text.insert(cursor_, text); - int cursor = position; + int cursor = cursor_; SetText(new_text.c_str()); SetCursor(cursor + text.length()); - UpdateCursorLocation(); + QueueRefresh (true, true); } } -void IMTextEntry::OnCommit(GtkIMContext* context, char* str) -{ - LOG_DEBUG(logger) << "Commit: " << str; - DeleteSelection(); - - if (str) - { - InsertTextAt(cursor_, std::string(str)); - } -} - -void IMTextEntry::OnPreeditChanged(GtkIMContext* context) -{ - glib::String preedit; - int cursor_pos = -1; - - gtk_im_context_get_preedit_string(context, &preedit, &preedit_attrs_, &cursor_pos); - - LOG_DEBUG(logger) << "Preedit changed: " << preedit; - - preedit_ = preedit.Str(); - - if (!preedit.Str().empty()) { - preedit_cursor_ = preedit.Str().length(); - QueueRefresh(true, true); - sigTextChanged.emit(this); - UpdateCursorLocation(); - } -} - -void IMTextEntry::OnPreeditStart(GtkIMContext* context) -{ - im_active = true; - - LOG_DEBUG(logger) << "Preedit start"; -} - -void IMTextEntry::OnPreeditEnd(GtkIMContext* context) -{ - im_active = false; - ResetPreedit(); - gtk_im_context_reset(im_context_); - - QueueRefresh(true, true); - sigTextChanged.emit(this); - - LOG_DEBUG(logger) << "Preedit ended"; -} - -void IMTextEntry::OnFocusIn() -{ - focused_ = true; - gtk_im_context_focus_in(im_context_); - gtk_im_context_reset(im_context_); - UpdateCursorLocation(); -} - -void IMTextEntry::OnFocusOut() -{ - focused_ = false; - gtk_im_context_focus_out(im_context_); -} - -void IMTextEntry::UpdateCursorLocation() -{ - nux::Rect strong, weak; - GetCursorRects(&strong, &weak); - nux::Geometry geo = GetGeometry(); - - GdkRectangle area = { strong.x + geo.x, strong.y + geo.y, strong.width, strong.height }; - gtk_im_context_set_cursor_location(im_context_, &area); -} - void IMTextEntry::OnMouseButtonUp(int x, int y, unsigned long bflags, unsigned long kflags) { int button = nux::GetEventButton(bflags); - if (im_enabled && button == 3) - { - GtkWidget* menu = gtk_menu_new(); - gtk_im_multicontext_append_menuitems(glib::object_cast<GtkIMMulticontext>(im_context_), - GTK_MENU_SHELL(menu)); - gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL, 3, GDK_CURRENT_TIME); - } - else if (button == 2) - { - SetCursor(XYToTextIndex(x, y)); - UpdateCursorLocation(); + if (button == 2) + { + SetCursor(XYToTextIndex(x,y)); Paste(true); - } + } } - } diff --git a/plugins/unityshell/src/IMTextEntry.h b/plugins/unityshell/src/IMTextEntry.h index 5b9a7d8a0..11b17cfb4 100644 --- a/plugins/unityshell/src/IMTextEntry.h +++ b/plugins/unityshell/src/IMTextEntry.h @@ -40,42 +40,15 @@ class IMTextEntry : public nux::TextEntry public: IMTextEntry(); - nux::Property<std::string> preedit_string; - nux::Property<bool> im_enabled; - nux::Property<bool> im_active; - private: - void CheckIMEnabled(); - void SetupSimpleIM(); - void SetupMultiIM(); - bool InspectKeyEvent(unsigned int eventType, unsigned int keysym, const char* character); - bool TryHandleEvent(unsigned int eventType, unsigned int keysym, const char* character); - void KeyEventToGdkEventKey(Event& event, GdkEventKey& gdk_event); - inline void CheckValidClientWindow(Window window); bool TryHandleSpecial(unsigned int eventType, unsigned int keysym, const char* character); - void InsertTextAt(unsigned int position, std::string const& text); + void InsertText(std::string const& text); void Cut(); void Copy(); void Paste(bool primary = false); - void OnCommit(GtkIMContext* context, char* str); - void OnPreeditChanged(GtkIMContext* context); - void OnPreeditStart(GtkIMContext* context); - void OnPreeditEnd(GtkIMContext* context); - - void OnFocusIn(); - void OnFocusOut(); - - void UpdateCursorLocation(); - void OnMouseButtonUp(int x, int y, unsigned long bflags, unsigned long kflags); - - private: - glib::SignalManager sig_manager_; - glib::Object<GtkIMContext> im_context_; - glib::Object<GdkWindow> client_window_; - bool focused_; }; } diff --git a/plugins/unityshell/src/IntrospectableWrappers.cpp b/plugins/unityshell/src/IntrospectableWrappers.cpp new file mode 100644 index 000000000..dbf5b0de0 --- /dev/null +++ b/plugins/unityshell/src/IntrospectableWrappers.cpp @@ -0,0 +1,50 @@ +// -*- Mode: C++; indent-tabs-mode: nil; tab-width: 2 -*- +/* + * Copyright (C) 2012 Canonical Ltd + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * Authored by: Thomi Richards <thomi.richards@canonical.com> + */ + +#include <UnityCore/Variant.h> + +#include "IntrospectableWrappers.h" + +namespace unity +{ +namespace debug +{ + ResultWrapper::ResultWrapper(const dash::Result& result) + : uri_(result.uri), + name_(result.name), + icon_hint_(result.icon_hint), + mime_type_(result.mimetype) + { + } + + std::string ResultWrapper::GetName() const + { + return "Result"; + } + + void ResultWrapper::AddProperties(GVariantBuilder* builder) + { + unity::variant::BuilderWrapper(builder) + .add("uri", uri_) + .add("name", name_) + .add("icon_hint", icon_hint_) + .add("mimetype", mime_type_); + } +} +} diff --git a/plugins/unityshell/src/IntrospectableWrappers.h b/plugins/unityshell/src/IntrospectableWrappers.h new file mode 100644 index 000000000..a999e52bb --- /dev/null +++ b/plugins/unityshell/src/IntrospectableWrappers.h @@ -0,0 +1,51 @@ +// -*- Mode: C++; indent-tabs-mode: nil; tab-width: 2 -*- +/* + * Copyright (C) 2012 Canonical Ltd + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * Authored by: Thomi Richards <thomi.richards@canonical.com> + */ + +#ifndef _INTROSPECTABLE_WRAPPERS_H +#define _INTROSPECTABLE_WRAPPERS_H + +#include <UnityCore/Result.h> + +#include "Introspectable.h" + +namespace unity +{ +namespace debug +{ + +/** Wrap a result object from UnityCore and present it's properties + * to the introspectable tree. + */ +class ResultWrapper: public Introspectable +{ +public: + ResultWrapper(const dash::Result& result); + std::string GetName() const; + void AddProperties(GVariantBuilder* builder); +private: + std::string uri_; + std::string name_; + std::string icon_hint_; + std::string mime_type_; +}; + +} +} + +#endif diff --git a/plugins/unityshell/src/Launcher.cpp b/plugins/unityshell/src/Launcher.cpp index dfefaaf61..d72abbea1 100644 --- a/plugins/unityshell/src/Launcher.cpp +++ b/plugins/unityshell/src/Launcher.cpp @@ -407,7 +407,8 @@ Launcher::AddProperties(GVariantBuilder* builder) .add("quicklist-open", _hide_machine->GetQuirk(LauncherHideMachine::QUICKLIST_OPEN)) .add("hide-quirks", _hide_machine->DebugHideQuirks().c_str()) .add("hover-quirks", _hover_machine->DebugHoverQuirks().c_str()) - .add("icon-size", _icon_size); + .add("icon-size", _icon_size) + .add("shortcuts_shown", _shortcuts_shown); } void Launcher::SetMousePosition(int x, int y) @@ -1015,7 +1016,8 @@ void Launcher::FillRenderArg(AbstractLauncherIcon::Ptr icon, float center_transit_progress = IconCenterTransitionProgress(icon, current); if (center_transit_progress <= 1.0f) { - centerOffset.y = (icon->GetSavedCenter(monitor).y - (center.y + (half_size * size_modifier))) * (1.0f - center_transit_progress); + int saved_center = icon->GetSavedCenter(monitor).y - parent_abs_geo.y; + centerOffset.y = (saved_center - (center.y + (half_size * size_modifier))) * (1.0f - center_transit_progress); } center.y += half_size * size_modifier; // move to center @@ -2009,7 +2011,12 @@ gboolean Launcher::StartIconDragTimeout(gpointer data) void Launcher::StartIconDragRequest(int x, int y) { - AbstractLauncherIcon::Ptr drag_icon = MouseIconIntersection((int)(GetGeometry().x / 2.0f), y); + nux::Geometry geo = GetAbsoluteGeometry(); + AbstractLauncherIcon::Ptr drag_icon = MouseIconIntersection((int)(GetGeometry().width / 2.0f), y); + + x += geo.x; + y += geo.y; + // FIXME: nux doesn't give nux::GetEventButton (button_flags) there, relying // on an internal Launcher property then @@ -2020,7 +2027,7 @@ void Launcher::StartIconDragRequest(int x, int y) UpdateDragWindowPosition(drag_icon->GetCenter(monitor).x, drag_icon->GetCenter(monitor).y); if (_initial_drag_animation) { - _drag_window->SetAnimationTarget(x, y + _drag_window->GetGeometry().height / 2); + _drag_window->SetAnimationTarget(x, y); _drag_window->StartAnimation(); } EnsureAnimation(); @@ -2107,9 +2114,9 @@ void Launcher::UpdateDragWindowPosition(int x, int y) if (_drag_window) { nux::Geometry geo = _drag_window->GetGeometry(); - _drag_window->SetBaseXY(x - geo.width / 2 + _parent->GetGeometry().x, y - geo.height / 2 + _parent->GetGeometry().y); + _drag_window->SetBaseXY(x - geo.width / 2, y - geo.height / 2); - AbstractLauncherIcon::Ptr hovered_icon = MouseIconIntersection((int)(GetGeometry().x / 2.0f), y); + AbstractLauncherIcon::Ptr hovered_icon = MouseIconIntersection((int)((GetGeometry().x + GetGeometry().width) / 2.0f), y - GetAbsoluteGeometry().y); struct timespec current; clock_gettime(CLOCK_MONOTONIC, ¤t); @@ -2203,7 +2210,8 @@ void Launcher::RecvMouseDrag(int x, int y, int dx, int dy, unsigned long button_ } else if (GetActionState() == ACTION_DRAG_ICON) { - UpdateDragWindowPosition(x, y); + nux::Geometry geo = GetAbsoluteGeometry(); + UpdateDragWindowPosition(geo.x + x, geo.y + y); } EnsureAnimation(); @@ -2290,6 +2298,21 @@ void Launcher::OnPointerBarrierEvent(ui::PointerBarrierWrapper* owner, ui::Barri if (apply_to_reveal) { + int root_x_return, root_y_return, win_x_return, win_y_return; + unsigned int mask_return; + Window root_return, child_return; + Display *dpy = nux::GetGraphicsDisplay()->GetX11Display(); + + if (XQueryPointer (dpy, DefaultRootWindow(dpy), &root_return, &child_return, &root_x_return, + &root_y_return, &win_x_return, &win_y_return, &mask_return)) + { + if (mask_return & (Button1Mask | Button3Mask)) + apply_to_reveal = false; + } + } + + if (apply_to_reveal) + { _hide_machine->AddRevealPressure(event->velocity); decaymulator_->value = 0; } diff --git a/plugins/unityshell/src/LauncherController.cpp b/plugins/unityshell/src/LauncherController.cpp index 06d278845..3fd98420e 100644 --- a/plugins/unityshell/src/LauncherController.cpp +++ b/plugins/unityshell/src/LauncherController.cpp @@ -908,9 +908,9 @@ void Controller::HandleLauncherKeyPress() pimpl->launcher_label_show_handler_id_ = g_timeout_add(local::shortcuts_show_delay, show_shortcuts, pimpl); } -void Controller::HandleLauncherKeyRelease() +void Controller::HandleLauncherKeyRelease(bool was_tap) { - if (pimpl->TapTimeUnderLimit()) + if (pimpl->TapTimeUnderLimit() && was_tap) { pimpl->SendHomeActivationRequest(); } @@ -1022,7 +1022,6 @@ void Controller::KeyNavActivate() pimpl->launcher_keynav = true; pimpl->keynav_restore_window_ = true; pimpl->keyboard_launcher_ = pimpl->launchers[pimpl->MonitorWithMouse()]; - pimpl->keyboard_launcher_->ShowShortcuts(false); pimpl->keyboard_launcher_->EnterKeyNavMode(); pimpl->model_->SetSelection(0); @@ -1087,7 +1086,8 @@ Controller::AddProperties(GVariantBuilder* builder) .add("key_nav_is_active", KeyNavIsActive()) .add("key_nav_launcher_monitor", pimpl->keyboard_launcher_.IsValid() ? pimpl->keyboard_launcher_->monitor : -1) .add("key_nav_selection", pimpl->model_->SelectionIndex()) - .add("key_nav_is_grabbed", pimpl->launcher_grabbed); + .add("key_nav_is_grabbed", pimpl->launcher_grabbed) + .add("keyboard_launcher", pimpl->launchers[pimpl->MonitorWithMouse()]->monitor); } void Controller::Impl::ReceiveLauncherKeyPress(unsigned long eventType, diff --git a/plugins/unityshell/src/LauncherController.h b/plugins/unityshell/src/LauncherController.h index 06cd7e66d..0ec3c1607 100644 --- a/plugins/unityshell/src/LauncherController.h +++ b/plugins/unityshell/src/LauncherController.h @@ -60,7 +60,7 @@ public: void SetShowDesktopIcon(bool show_desktop_icon); void HandleLauncherKeyPress(); - void HandleLauncherKeyRelease(); + void HandleLauncherKeyRelease(bool was_tap); bool HandleLauncherKeyEvent(Display *display, unsigned int key_sym, unsigned long key_code, diff --git a/plugins/unityshell/src/LauncherIcon.cpp b/plugins/unityshell/src/LauncherIcon.cpp index a7b65e942..a2221f42f 100644 --- a/plugins/unityshell/src/LauncherIcon.cpp +++ b/plugins/unityshell/src/LauncherIcon.cpp @@ -99,7 +99,7 @@ LauncherIcon::LauncherIcon() // FIXME: the abstraction is already broken, should be fixed for O // right now, hooking the dynamic quicklist the less ugly possible way - + mouse_enter.connect(sigc::mem_fun(this, &LauncherIcon::RecvMouseEnter)); mouse_leave.connect(sigc::mem_fun(this, &LauncherIcon::RecvMouseLeave)); @@ -149,7 +149,7 @@ void LauncherIcon::LoadTooltip() _tooltip = new Tooltip(); AddChild(_tooltip.GetPointer()); - _tooltip->SetText(nux::NString(tooltip_text().c_str())); + _tooltip->SetText(tooltip_text()); } void LauncherIcon::LoadQuicklist() @@ -454,7 +454,7 @@ bool LauncherIcon::SetTooltipText(std::string& target, std::string const& value) { target = escaped; if (_tooltip) - _tooltip->SetText(nux::NString(target.c_str())); + _tooltip->SetText(target); result = true; } diff --git a/plugins/unityshell/src/LauncherModel.cpp b/plugins/unityshell/src/LauncherModel.cpp index 71573ca40..5415e6c95 100644 --- a/plugins/unityshell/src/LauncherModel.cpp +++ b/plugins/unityshell/src/LauncherModel.cpp @@ -215,7 +215,10 @@ LauncherModel::IconHasSister(AbstractLauncherIcon::Ptr icon) const void LauncherModel::ReorderAfter(AbstractLauncherIcon::Ptr icon, AbstractLauncherIcon::Ptr other) { - if (icon == other) + if (icon == other || icon.IsNull() || other.IsNull()) + return; + + if (icon->GetIconType() != other->GetIconType()) return; int i = 0; @@ -245,7 +248,10 @@ LauncherModel::ReorderAfter(AbstractLauncherIcon::Ptr icon, AbstractLauncherIcon void LauncherModel::ReorderBefore(AbstractLauncherIcon::Ptr icon, AbstractLauncherIcon::Ptr other, bool save) { - if (icon == other) + if (icon == other || icon.IsNull() || other.IsNull()) + return; + + if (icon->GetIconType() != other->GetIconType()) return; int i = 0; @@ -286,7 +292,10 @@ LauncherModel::ReorderBefore(AbstractLauncherIcon::Ptr icon, AbstractLauncherIco void LauncherModel::ReorderSmart(AbstractLauncherIcon::Ptr icon, AbstractLauncherIcon::Ptr other, bool save) { - if (icon == other) + if (icon == other || icon.IsNull() || other.IsNull()) + return; + + if (icon->GetIconType() != other->GetIconType()) return; int i = 0; diff --git a/plugins/unityshell/src/LensBar.cpp b/plugins/unityshell/src/LensBar.cpp index e8e6c6cef..5e42ae034 100644 --- a/plugins/unityshell/src/LensBar.cpp +++ b/plugins/unityshell/src/LensBar.cpp @@ -31,8 +31,9 @@ namespace nux::logging::Logger logger("unity.dash.lensbar"); -const int FOCUS_OVERLAY_WIDTH = 56; -const int FOCUS_OVERLAY_HEIGHT = 40; +const int FOCUS_OVERLAY_WIDTH = 60; +const int FOCUS_OVERLAY_HEIGHT = 44; +const int LENSBAR_HEIGHT = 44; } @@ -52,7 +53,6 @@ void LensBar::InitTheme() if (!focus_layer_) { focus_layer_.reset(Style::Instance().FocusOverlay(FOCUS_OVERLAY_WIDTH, FOCUS_OVERLAY_HEIGHT)); - over_layer_.reset(Style::Instance().FocusOverlay(FOCUS_OVERLAY_WIDTH, FOCUS_OVERLAY_HEIGHT)); } } @@ -72,8 +72,8 @@ void LensBar::SetupLayout() layout_->SetSpaceBetweenChildren(40); SetLayout(layout_); - SetMinimumHeight(46); - SetMaximumHeight(46); + SetMinimumHeight(LENSBAR_HEIGHT); + SetMaximumHeight(LENSBAR_HEIGHT); } void LensBar::SetupHomeLens() @@ -85,8 +85,6 @@ void LensBar::SetupHomeLens() layout_->AddView(icon, 0, nux::eCenter, nux::MINOR_SIZE_FULL); icon->mouse_click.connect([&, icon] (int x, int y, unsigned long button, unsigned long keyboard) { SetActive(icon); QueueDraw(); }); - icon->mouse_enter.connect([&] (int x, int y, unsigned long button, unsigned long keyboard) { QueueDraw(); }); - icon->mouse_leave.connect([&] (int x, int y, unsigned long button, unsigned long keyboard) { QueueDraw(); }); icon->mouse_down.connect([&] (int x, int y, unsigned long button, unsigned long keyboard) { QueueDraw(); }); icon->key_nav_focus_change.connect([&](nux::Area*, bool, nux::KeyNavDirection){ QueueDraw(); }); icon->key_nav_focus_activate.connect([&, icon](nux::Area*){ SetActive(icon); }); @@ -101,8 +99,6 @@ void LensBar::AddLens(Lens::Ptr& lens) layout_->AddView(icon, 0, nux::eCenter, nux::eFix); icon->mouse_click.connect([&, icon] (int x, int y, unsigned long button, unsigned long keyboard) { SetActive(icon); QueueDraw(); }); - icon->mouse_enter.connect([&] (int x, int y, unsigned long button, unsigned long keyboard) { QueueDraw(); }); - icon->mouse_leave.connect([&] (int x, int y, unsigned long button, unsigned long keyboard) { QueueDraw(); }); icon->mouse_down.connect([&] (int x, int y, unsigned long button, unsigned long keyboard) { QueueDraw(); }); icon->key_nav_focus_change.connect([&](nux::Area*, bool, nux::KeyNavDirection){ QueueDraw(); }); icon->key_nav_focus_activate.connect([&, icon](nux::Area*){ SetActive(icon); }); @@ -132,8 +128,7 @@ void LensBar::Draw(nux::GraphicsEngine& gfx_context, bool force_draw) for (auto icon : icons_) { - if ((icon->HasKeyFocus() || icon->IsMouseInside()) && - focus_layer_ && over_layer_) + if (icon->HasKeyFocus() && focus_layer_) { nux::Geometry geo(icon->GetGeometry()); @@ -143,7 +138,7 @@ void LensBar::Draw(nux::GraphicsEngine& gfx_context, bool force_draw) geo.width = FOCUS_OVERLAY_WIDTH; geo.height = FOCUS_OVERLAY_HEIGHT; - nux::AbstractPaintLayer* layer = icon->HasKeyFocus() ? focus_layer_.get() : over_layer_.get(); + nux::AbstractPaintLayer* layer = focus_layer_.get(); layer->SetGeometry(geo); layer->Renderlayer(gfx_context); @@ -155,17 +150,18 @@ void LensBar::Draw(nux::GraphicsEngine& gfx_context, bool force_draw) void LensBar::DrawContent(nux::GraphicsEngine& gfx_context, bool force_draw) { - gfx_context.PushClippingRectangle(GetGeometry()); + nux::Geometry const& base = GetGeometry(); + + gfx_context.PushClippingRectangle(base); if (!IsFullRedraw()) nux::GetPainter().PushLayer(gfx_context, bg_layer_->GetGeometry(), bg_layer_.get()); for (auto icon: icons_) { - if ((icon->HasKeyFocus() || icon->IsMouseInside()) && !IsFullRedraw() - && focus_layer_ && over_layer_) + if (icon->HasKeyFocus() && !IsFullRedraw() && focus_layer_) { - nux::AbstractPaintLayer* layer = icon->HasKeyFocus() ? focus_layer_.get() : over_layer_.get(); + nux::AbstractPaintLayer* layer = focus_layer_.get(); nux::GetPainter().PushLayer(gfx_context, focus_layer_->GetGeometry(), layer); } @@ -176,6 +172,28 @@ void LensBar::DrawContent(nux::GraphicsEngine& gfx_context, bool force_draw) if (!IsFullRedraw()) nux::GetPainter().PopBackground(); + for (auto icon: icons_) + { + if (icon->active) + { + nux::Geometry const& geo = icon->GetGeometry(); + int middle = geo.x + geo.width/2; + int size = 5; + // Nux doesn't draw too well the small triangles, so let's draw a + // bigger one and clip part of them using the "-1". + int y = base.y - 1; + + nux::GetPainter().Draw2DTriangleColor(gfx_context, + middle - size, y, + middle, y + size, + middle + size, y, + nux::color::White); + + break; + + } + } + gfx_context.PopClippingRectangle(); } diff --git a/plugins/unityshell/src/LensBar.h b/plugins/unityshell/src/LensBar.h index 8d48659b5..44c6466f0 100644 --- a/plugins/unityshell/src/LensBar.h +++ b/plugins/unityshell/src/LensBar.h @@ -81,7 +81,6 @@ private: nux::HLayout* layout_; LayerPtr bg_layer_; LayerPtr focus_layer_; - LayerPtr over_layer_; }; } // namespace dash diff --git a/plugins/unityshell/src/LensBarIcon.cpp b/plugins/unityshell/src/LensBarIcon.cpp index ce27dc90b..297d8ef2c 100644 --- a/plugins/unityshell/src/LensBarIcon.cpp +++ b/plugins/unityshell/src/LensBarIcon.cpp @@ -41,6 +41,7 @@ LensBarIcon::LensBarIcon(std::string id_, std::string icon_hint) SetAcceptKeyNavFocus(true); SetAcceptKeyNavFocusOnMouseDown(false); + SetAcceptKeyNavFocusOnMouseEnter(true); active.changed.connect(sigc::mem_fun(this, &LensBarIcon::OnActiveChanged)); } diff --git a/plugins/unityshell/src/LensView.cpp b/plugins/unityshell/src/LensView.cpp index b3cd70735..42396bff3 100644 --- a/plugins/unityshell/src/LensView.cpp +++ b/plugins/unityshell/src/LensView.cpp @@ -48,6 +48,8 @@ class LensScrollView: public nux::ScrollView public: LensScrollView(nux::VScrollBar* scroll_bar, NUX_FILE_LINE_DECL) : nux::ScrollView(NUX_FILE_LINE_PARAM) + , right_area_(nullptr) + , up_area_(nullptr) { SetVScrollBar(scroll_bar); } @@ -79,6 +81,35 @@ public: ScrollDown (1, size); } } + + void SetRightArea(nux::Area* area) + { + right_area_ = area; + } + + void SetUpArea(nux::Area* area) + { + up_area_ = area; + } + +protected: + + // This is so we can break the natural key navigation path. + nux::Area* KeyNavIteration(nux::KeyNavDirection direction) + { + nux::Area* focus_area = nux::GetWindowCompositor().GetKeyFocusArea(); + + if (direction == nux::KEY_NAV_RIGHT && focus_area && focus_area->IsChildOf(this)) + return right_area_; + else if (direction == nux::KEY_NAV_UP && focus_area && focus_area->IsChildOf(this)) + return up_area_; + else + return nux::ScrollView::KeyNavIteration(direction); + } + +private: + nux::Area* right_area_; + nux::Area* up_area_; }; @@ -92,7 +123,7 @@ LensView::LensView() , fix_renderering_id_(0) {} -LensView::LensView(Lens::Ptr lens) +LensView::LensView(Lens::Ptr lens, nux::Area* show_filters) : nux::View(NUX_TRACKER_LOCATION) , search_string("") , filters_expanded(false) @@ -101,7 +132,7 @@ LensView::LensView(Lens::Ptr lens) , initial_activation_(true) , fix_renderering_id_(0) { - SetupViews(); + SetupViews(show_filters); SetupCategories(); SetupResults(); SetupFilters(); @@ -124,7 +155,7 @@ LensView::LensView(Lens::Ptr lens) { if (category->GetLayout() != nullptr) { - auto expand_label = category->GetExpandLabel(); + auto expand_label = category->GetHeaderFocusableView(); auto child = category->GetChildView(); if ((child && child->HasKeyFocus()) || @@ -149,7 +180,7 @@ LensView::~LensView() g_source_remove(fix_renderering_id_); } -void LensView::SetupViews() +void LensView::SetupViews(nux::Area* show_filters) { layout_ = new nux::HLayout(NUX_TRACKER_LOCATION); @@ -161,18 +192,21 @@ void LensView::SetupViews() scroll_layout_ = new nux::VLayout(NUX_TRACKER_LOCATION); scroll_view_->SetLayout(scroll_layout_); + scroll_view_->SetRightArea(show_filters); fscroll_view_ = new LensScrollView(new PlacesVScrollBar(NUX_TRACKER_LOCATION), NUX_TRACKER_LOCATION); fscroll_view_->EnableVerticalScrollBar(true); fscroll_view_->EnableHorizontalScrollBar(false); fscroll_view_->SetVisible(false); + fscroll_view_->SetUpArea(show_filters); layout_->AddView(fscroll_view_, 1); fscroll_layout_ = new nux::VLayout(); fscroll_view_->SetLayout(fscroll_layout_); filter_bar_ = new FilterBar(); + AddChild(filter_bar_); fscroll_layout_->AddView(filter_bar_); SetLayout(layout_); @@ -398,6 +432,11 @@ Lens::Ptr LensView::lens() const return lens_; } +nux::Area* LensView::fscroll_view() const +{ + return fscroll_view_; +} + int LensView::GetNumRows() { unsigned int columns = dash::Style::Instance().GetDefaultNColumns(); @@ -461,7 +500,11 @@ std::string LensView::GetName() const } void LensView::AddProperties(GVariantBuilder* builder) -{} +{ + unity::variant::BuilderWrapper(builder) + .add("name", lens_->id) + .add("lens-name", lens_->name); +} } diff --git a/plugins/unityshell/src/LensView.h b/plugins/unityshell/src/LensView.h index 8112e4c8b..b0cde1cad 100644 --- a/plugins/unityshell/src/LensView.h +++ b/plugins/unityshell/src/LensView.h @@ -50,10 +50,14 @@ class LensView : public nux::View, public unity::debug::Introspectable public: LensView(); - LensView(Lens::Ptr lens); - virtual ~LensView(); + LensView(Lens::Ptr lens, nux::Area* show_filters); + ~LensView(); + CategoryGroups& categories() { return categories_; } + FilterBar* filter_bar() const { return filter_bar_; } Lens::Ptr lens() const; + nux::Area* fscroll_view() const; + int GetNumRows(); virtual void ActivateFirst(); @@ -66,7 +70,7 @@ public: sigc::signal<void, std::string const&> uri_activated; private: - void SetupViews(); + void SetupViews(nux::Area* show_filters); void SetupCategories(); void SetupResults(); void SetupFilters(); diff --git a/plugins/unityshell/src/PanelController.cpp b/plugins/unityshell/src/PanelController.cpp index af40a0155..be71af40e 100644 --- a/plugins/unityshell/src/PanelController.cpp +++ b/plugins/unityshell/src/PanelController.cpp @@ -22,6 +22,7 @@ #include <vector> #include <NuxCore/Logger.h> #include <Nux/BaseWindow.h> +#include <UnityCore/Variant.h> #include "UScreen.h" #include "PanelView.h" @@ -369,7 +370,8 @@ std::string Controller::GetName() const void Controller::AddProperties(GVariantBuilder* builder) { - g_variant_builder_add(builder, "{sv}", "opacity", g_variant_new_double(pimpl->opacity())); + variant::BuilderWrapper(builder) + .add("opacity", pimpl->opacity()); } void Controller::OnScreenChanged(int primary_monitor, std::vector<nux::Geometry>& monitors) diff --git a/plugins/unityshell/src/PanelMenuView.cpp b/plugins/unityshell/src/PanelMenuView.cpp index 90ba22e1e..559458c0b 100644 --- a/plugins/unityshell/src/PanelMenuView.cpp +++ b/plugins/unityshell/src/PanelMenuView.cpp @@ -61,9 +61,9 @@ PanelMenuView::PanelMenuView(int padding) _util_cg(CAIRO_FORMAT_ARGB32, 1, 1), _gradient_texture(nullptr), _is_inside(false), + _is_grabbed(false), _is_maximized(false), _is_own_window(false), - _is_grabbed(false), _last_active_view(nullptr), _new_application(nullptr), _last_width(0), diff --git a/plugins/unityshell/src/PlacesGroup.cpp b/plugins/unityshell/src/PlacesGroup.cpp index df904de9d..11f64f115 100644 --- a/plugins/unityshell/src/PlacesGroup.cpp +++ b/plugins/unityshell/src/PlacesGroup.cpp @@ -43,6 +43,7 @@ #include "DashStyle.h" #include "ubus-server.h" #include "UBusMessages.h" + #include "Introspectable.h" namespace unity { @@ -69,6 +70,8 @@ public: HeaderView(NUX_FILE_LINE_DECL) : nux::View(NUX_FILE_LINE_PARAM) { + SetAcceptKeyNavFocusOnMouseDown(false); + SetAcceptKeyNavFocusOnMouseEnter(true); } protected: @@ -84,7 +87,17 @@ protected: bool AcceptKeyNavFocus() { - return false; + return true; + } + + nux::Area* FindAreaUnderMouse(const nux::Point& mouse_position, nux::NuxEventType event_type) + { + bool mouse_inside = TestMousePointerInclusionFilterMouseWheel(mouse_position, event_type); + + if (mouse_inside == false) + return nullptr; + + return this; } }; @@ -102,6 +115,9 @@ PlacesGroup::PlacesGroup() _n_total_items(0), _draw_sep(true) { + SetAcceptKeyNavFocusOnMouseDown(false); + SetAcceptKeyNavFocusOnMouseEnter(false); + nux::BaseTexture* arrow = dash::Style::Instance().GetGroupUnexpandIcon(); _cached_name = NULL; @@ -151,26 +167,24 @@ PlacesGroup::PlacesGroup() SetLayout(_group_layout); // don't need to disconnect these signals as they are disconnected when this object destroys the contents - _header_view->mouse_enter.connect(sigc::mem_fun(this, &PlacesGroup::RecvMouseEnter)); - _header_view->mouse_leave.connect(sigc::mem_fun(this, &PlacesGroup::RecvMouseLeave)); _header_view->mouse_click.connect(sigc::mem_fun(this, &PlacesGroup::RecvMouseClick)); + _header_view->key_nav_focus_change.connect(sigc::mem_fun(this, &PlacesGroup::OnLabelFocusChanged)); + _header_view->key_nav_focus_activate.connect(sigc::mem_fun(this, &PlacesGroup::OnLabelActivated)); _icon->mouse_click.connect(sigc::mem_fun(this, &PlacesGroup::RecvMouseClick)); - _icon->mouse_enter.connect(sigc::mem_fun(this, &PlacesGroup::RecvMouseEnter)); - _icon->mouse_leave.connect(sigc::mem_fun(this, &PlacesGroup::RecvMouseLeave)); - _icon->key_nav_focus_change.connect(sigc::mem_fun(this, &PlacesGroup::OnLabelFocusChanged)); _name->mouse_click.connect(sigc::mem_fun(this, &PlacesGroup::RecvMouseClick)); - _name->mouse_enter.connect(sigc::mem_fun(this, &PlacesGroup::RecvMouseEnter)); - _name->mouse_leave.connect(sigc::mem_fun(this, &PlacesGroup::RecvMouseLeave)); - _name->key_nav_focus_change.connect(sigc::mem_fun(this, &PlacesGroup::OnLabelFocusChanged)); _expand_label->mouse_click.connect(sigc::mem_fun(this, &PlacesGroup::RecvMouseClick)); - _expand_label->mouse_enter.connect(sigc::mem_fun(this, &PlacesGroup::RecvMouseEnter)); - _expand_label->mouse_leave.connect(sigc::mem_fun(this, &PlacesGroup::RecvMouseLeave)); - _expand_label->key_nav_focus_activate.connect(sigc::mem_fun(this, &PlacesGroup::OnLabelActivated)); - _expand_label->key_nav_focus_change.connect(sigc::mem_fun(this, &PlacesGroup::OnLabelFocusChanged)); _expand_icon->mouse_click.connect(sigc::mem_fun(this, &PlacesGroup::RecvMouseClick)); - _expand_icon->mouse_enter.connect(sigc::mem_fun(this, &PlacesGroup::RecvMouseEnter)); - _expand_icon->mouse_leave.connect(sigc::mem_fun(this, &PlacesGroup::RecvMouseLeave)); - _expand_icon->key_nav_focus_change.connect(sigc::mem_fun(this, &PlacesGroup::OnLabelFocusChanged)); + + key_nav_focus_change.connect([&](nux::Area* area, bool has_focus, nux::KeyNavDirection direction) + { + if (!has_focus) + return; + + if(direction == nux::KEY_NAV_UP) + nux::GetWindowCompositor().SetKeyFocusArea(_child_view, direction); + else + nux::GetWindowCompositor().SetKeyFocusArea(GetHeaderFocusableView(), direction); + }); } PlacesGroup::~PlacesGroup() @@ -193,8 +207,11 @@ PlacesGroup::OnLabelActivated(nux::Area* label) void PlacesGroup::OnLabelFocusChanged(nux::Area* label, bool has_focus, nux::KeyNavDirection direction) { - _ubus.SendMessage(UBUS_RESULT_VIEW_KEYNAV_CHANGED, - g_variant_new("(iiii)", 0, -30, 0, -30)); + if (HeaderHasKeyFocus()) + { + _ubus.SendMessage(UBUS_RESULT_VIEW_KEYNAV_CHANGED, + g_variant_new("(iiii)", 0, 0, 0, 0)); + } QueueDraw(); } @@ -239,6 +256,9 @@ PlacesGroup::SetIcon(const char* path_to_emblem) void PlacesGroup::SetChildView(nux::View* view) { + debug::Introspectable *i = dynamic_cast<debug::Introspectable*>(view); + if (i) + AddChild(i); _child_view = view; _group_layout->AddView(_child_view, 1); QueueDraw(); @@ -291,20 +311,6 @@ PlacesGroup::RefreshLabel() _expand_label->SetText(final); _expand_label->SetVisible(_n_visible_items_in_unexpand_mode < _n_total_items); - _icon->SetAcceptKeyNavFocus(false); - _name->SetAcceptKeyNavFocus(false); - _expand_label->SetAcceptKeyNavFocus(false); - _expand_icon->SetAcceptKeyNavFocus(false); - - if (_expand_label->IsVisible()) - _expand_label->SetAcceptKeyNavFocus(true); - else if (_expand_icon->IsVisible()) - _expand_icon->SetAcceptKeyNavFocus(true); - else if (_name->IsVisible()) - _name->SetAcceptKeyNavFocus(true); - else if (_icon->IsVisible()) - _icon->SetAcceptKeyNavFocus(true); - QueueDraw(); g_free((result_string)); @@ -492,16 +498,22 @@ PlacesGroup::SetDrawSeparator(bool draw_it) bool PlacesGroup::HeaderHasKeyFocus() const { - return (_icon && _icon->HasKeyFocus()) || - (_name && _name->HasKeyFocus()) || - (_expand_label && _expand_label->HasKeyFocus()) || - (_expand_icon && _expand_icon->HasKeyFocus()); + return (_header_view && _header_view->HasKeyFocus()); +} + +bool PlacesGroup::HeaderIsFocusable() const +{ + return (_header_view != nullptr); +} + +nux::View* PlacesGroup::GetHeaderFocusableView() const +{ + return _header_view; } bool PlacesGroup::ShouldBeHighlighted() const { - return (_header_view && _header_view->IsMousePointerInside()) || - HeaderHasKeyFocus(); + return HeaderHasKeyFocus(); } // @@ -510,7 +522,7 @@ bool PlacesGroup::ShouldBeHighlighted() const bool PlacesGroup::AcceptKeyNavFocus() { - return false; + return true; } // @@ -531,6 +543,9 @@ void PlacesGroup::AddProperties(GVariantBuilder* builder) wrapper.add("header-height", _header_view->GetAbsoluteHeight()); wrapper.add("header-has-keyfocus", HeaderHasKeyFocus()); wrapper.add("header-is-highlighted", ShouldBeHighlighted()); + wrapper.add("name", _name->GetText()); + wrapper.add("is-visible", IsVisible()); + wrapper.add("is-expanded", GetExpanded()); } } // namespace unity diff --git a/plugins/unityshell/src/PlacesGroup.h b/plugins/unityshell/src/PlacesGroup.h index 6f010bc31..16ba7b598 100644 --- a/plugins/unityshell/src/PlacesGroup.h +++ b/plugins/unityshell/src/PlacesGroup.h @@ -67,6 +67,8 @@ public: bool GetExpanded() const; int GetHeaderHeight() const; + bool HeaderIsFocusable() const; + nux::View* GetHeaderFocusableView() const; void SetDrawSeparator(bool draw_it); diff --git a/plugins/unityshell/src/PluginAdapter.cpp b/plugins/unityshell/src/PluginAdapter.cpp index 82e18fd92..14db81474 100644 --- a/plugins/unityshell/src/PluginAdapter.cpp +++ b/plugins/unityshell/src/PluginAdapter.cpp @@ -579,6 +579,7 @@ PluginAdapter::FocusWindowGroup(std::vector<Window> window_ids, FocusVisibility CompWindow* top_window_on_monitor = NULL; bool any_on_current = false; bool any_mapped = false; + bool any_mapped_on_current = false; bool forced_unminimize = false; /* sort the list */ @@ -596,6 +597,11 @@ PluginAdapter::FocusWindowGroup(std::vector<Window> window_ids, FocusVisibility if (win->defaultViewport() == m_Screen->vp()) { any_on_current = true; + + if (!win->minimized()) + { + any_mapped_on_current = true; + } } if (!win->minimized()) @@ -644,7 +650,7 @@ PluginAdapter::FocusWindowGroup(std::vector<Window> window_ids, FocusVisibility if (!is_mapped) win->raise (); } - else if ((any_mapped && !win->minimized()) || !any_mapped) + else if ((any_mapped_on_current && !win->minimized()) || !any_mapped_on_current) { if (!forced_unminimize || target_vp == m_Screen->vp()) { diff --git a/plugins/unityshell/src/PreviewBase.h b/plugins/unityshell/src/PreviewBase.h index 17a19eb1e..f65e6fe1f 100644 --- a/plugins/unityshell/src/PreviewBase.h +++ b/plugins/unityshell/src/PreviewBase.h @@ -28,8 +28,6 @@ #include <Nux/View.h> #include <UnityCore/Preview.h> -#include "FilterWidget.h" - namespace unity { class PreviewBase : public nux::View diff --git a/plugins/unityshell/src/PreviewBasicButton.h b/plugins/unityshell/src/PreviewBasicButton.h index f4addd76a..02f04123e 100644 --- a/plugins/unityshell/src/PreviewBasicButton.h +++ b/plugins/unityshell/src/PreviewBasicButton.h @@ -27,7 +27,6 @@ #include <Nux/Nux.h> #include <Nux/Button.h> #include <Nux/CairoWrapper.h> -#include "FilterWidget.h" namespace unity { diff --git a/plugins/unityshell/src/QuicklistView.cpp b/plugins/unityshell/src/QuicklistView.cpp index 24ea7d6af..945a35105 100644 --- a/plugins/unityshell/src/QuicklistView.cpp +++ b/plugins/unityshell/src/QuicklistView.cpp @@ -28,6 +28,7 @@ #include <NuxGraphics/GraphicsEngine.h> #include <Nux/TextureArea.h> #include <NuxImage/CairoGraphics.h> +#include <UnityCore/Variant.h> #include "CairoTexture.h" @@ -55,7 +56,7 @@ NUX_IMPLEMENT_OBJECT_TYPE(QuicklistView); QuicklistView::QuicklistView() : _anchorX(0) , _anchorY(0) - , _labelText(TEXT("QuicklistView 1234567890")) + , _labelText("QuicklistView 1234567890") , _top_size(4) , _mouse_down(false) , _enable_quicklist_for_testing(false) @@ -1345,7 +1346,7 @@ void QuicklistView::NotifyConfigurationChange(int width, int height) { } -void QuicklistView::SetText(nux::NString text) +void QuicklistView::SetText(std::string const& text) { if (_labelText == text) return; @@ -1399,11 +1400,12 @@ std::string QuicklistView::GetName() const void QuicklistView::AddProperties(GVariantBuilder* builder) { - g_variant_builder_add(builder, "{sv}", "x", g_variant_new_int32(GetBaseX())); - g_variant_builder_add(builder, "{sv}", "y", g_variant_new_int32(GetBaseY())); - g_variant_builder_add(builder, "{sv}", "width", g_variant_new_int32(GetBaseWidth())); - g_variant_builder_add(builder, "{sv}", "height", g_variant_new_int32(GetBaseHeight())); - g_variant_builder_add(builder, "{sv}", "active", g_variant_new_boolean(IsVisible())); + variant::BuilderWrapper(builder) + .add("x", GetBaseX()) + .add("y", GetBaseY()) + .add("width", GetBaseWidth()) + .add("height", GetBaseHeight()) + .add("active", IsVisible()); } // diff --git a/plugins/unityshell/src/QuicklistView.h b/plugins/unityshell/src/QuicklistView.h index 686de09b1..fd60d20a8 100644 --- a/plugins/unityshell/src/QuicklistView.h +++ b/plugins/unityshell/src/QuicklistView.h @@ -45,7 +45,7 @@ public: QuicklistView(); ~QuicklistView(); - void SetText(nux::NString text); + void SetText(std::string const& text); void RemoveAllMenuItem(); @@ -135,7 +135,7 @@ private: //nux::CairoGraphics* _cairo_graphics; int _anchorX; int _anchorY; - nux::NString _labelText; + std::string _labelText; int _top_size; // size of the segment from point 13 to 14. See figure in ql_compute_full_mask_path. bool _mouse_down; diff --git a/plugins/unityshell/src/ResultView.cpp b/plugins/unityshell/src/ResultView.cpp index 1a86476d0..f09287910 100644 --- a/plugins/unityshell/src/ResultView.cpp +++ b/plugins/unityshell/src/ResultView.cpp @@ -22,6 +22,7 @@ #include "ResultView.h" +#include "IntrospectableWrappers.h" #include <Nux/HLayout.h> #include <Nux/VLayout.h> @@ -67,6 +68,8 @@ ResultView::ResultView(NUX_FILE_LINE_DECL) ResultView::~ResultView() { + ClearIntrospectableWrappers(); + for (auto result : results_) { renderer_->Unload(result); @@ -221,5 +224,37 @@ void ResultView::DrawContent(nux::GraphicsEngine& GfxContent, bool force_draw) GfxContent.PopClippingRectangle(); } +std::string ResultView::GetName() const +{ + return "ResultView"; +} + +void ResultView::AddProperties(GVariantBuilder* builder) +{ + unity::variant::BuilderWrapper(builder) + .add("expanded", expanded); +} + +debug::Introspectable::IntrospectableList const& ResultView::GetIntrospectableChildren() +{ + ClearIntrospectableWrappers(); + + for (auto result: results_) + { + introspectable_children_.push_back(new debug::ResultWrapper(result)); + } + return introspectable_children_; +} + +void ResultView::ClearIntrospectableWrappers() +{ + // delete old results, then add new results + for (auto old_result: introspectable_children_) + { + delete old_result; + } + introspectable_children_.clear(); +} + } } diff --git a/plugins/unityshell/src/ResultView.h b/plugins/unityshell/src/ResultView.h index c70c2316e..2e9223592 100644 --- a/plugins/unityshell/src/ResultView.h +++ b/plugins/unityshell/src/ResultView.h @@ -30,6 +30,7 @@ #include <UnityCore/GLibSignal.h> #include <UnityCore/Results.h> +#include "Introspectable.h" #include "PreviewBase.h" #include "ResultRenderer.h" @@ -37,7 +38,7 @@ namespace unity { namespace dash { -class ResultView : public nux::View +class ResultView : public nux::View, public debug::Introspectable { public: NUX_DECLARE_OBJECT_TYPE(ResultView, nux::View); @@ -60,6 +61,10 @@ public: sigc::signal<void, std::string const&> UriActivated; sigc::signal<void, std::string const&> ChangePreview; // request a new preview, string is the uri + std::string GetName() const; + void AddProperties(GVariantBuilder* builder); + IntrospectableList const& GetIntrospectableChildren(); + protected: virtual void Draw(nux::GraphicsEngine& GfxContext, bool force_draw); virtual void DrawContent(nux::GraphicsEngine& GfxContext, bool force_draw); @@ -71,6 +76,10 @@ protected: std::string preview_result_uri_; ResultRenderer* renderer_; ResultList results_; + IntrospectableList introspectable_children_; + +private: + void ClearIntrospectableWrappers(); }; } diff --git a/plugins/unityshell/src/ResultViewGrid.cpp b/plugins/unityshell/src/ResultViewGrid.cpp index fb768ed5f..59abb0276 100644 --- a/plugins/unityshell/src/ResultViewGrid.cpp +++ b/plugins/unityshell/src/ResultViewGrid.cpp @@ -361,15 +361,19 @@ bool ResultViewGrid::InspectKeyEvent(unsigned int eventType, unsigned int keysym int total_rows = std::ceil(results_.size() / static_cast<float>(items_per_row)); // items per row is always at least 1 total_rows = (expanded) ? total_rows : 1; // restrict to one row if not expanded - // check for edge cases where we want the keynav to bubble up - if (direction == nux::KEY_NAV_UP && selected_index_ < items_per_row) + // check for edge cases where we want the keynav to bubble up + if (direction == nux::KEY_NAV_LEFT && (selected_index_ % items_per_row == 0)) + return false; // pressed left on the first item, no diiice + else if (direction == nux::KEY_NAV_RIGHT && (selected_index_ == static_cast<int>(results_.size() - 1))) + return false; // pressed right on the last item, nope. nothing for you + else if (direction == nux::KEY_NAV_RIGHT && (selected_index_ % items_per_row) == (items_per_row - 1)) + return false; // pressed right on the last item in the first row in non expanded mode. nothing doing. + else if (direction == nux::KEY_NAV_UP && selected_index_ < items_per_row) return false; // key nav up when already on top row else if (direction == nux::KEY_NAV_DOWN && selected_index_ >= (total_rows-1) * items_per_row) return false; // key nav down when on bottom row - else - return true; - return false; + return true; } bool ResultViewGrid::AcceptKeyNavFocus() @@ -522,9 +526,14 @@ void ResultViewGrid::OnKeyNavFocusChange(nux::Area *area, bool has_focus, nux::K focused_y = (renderer_->height + vertical_spacing) * (selected_index_ / items_per_row); } - ubus_.SendMessage(UBUS_RESULT_VIEW_KEYNAV_CHANGED, - g_variant_new("(iiii)", focused_x, focused_y, renderer_->width(), renderer_->height())); + if (direction != nux::KEY_NAV_NONE) + { + ubus_.SendMessage(UBUS_RESULT_VIEW_KEYNAV_CHANGED, + g_variant_new("(iiii)", focused_x, focused_y, renderer_->width(), renderer_->height())); + } + selection_change.emit(); + } else { @@ -629,11 +638,7 @@ void ResultViewGrid::Draw(nux::GraphicsEngine& GfxContext, bool force_draw) break; ResultRenderer::ResultRendererState state = ResultRenderer::RESULT_RENDERER_NORMAL; - if ((int)(index) == mouse_over_index_) - { - state = ResultRenderer::RESULT_RENDERER_PRELIGHT; - } - else if ((int)(index) == selected_index_) + if ((int)(index) == selected_index_) { state = ResultRenderer::RESULT_RENDERER_SELECTED; } @@ -699,8 +704,12 @@ void ResultViewGrid::DrawContent(nux::GraphicsEngine& GfxContent, bool force_dra void ResultViewGrid::MouseMove(int x, int y, int dx, int dy, unsigned long button_flags, unsigned long key_flags) { uint index = GetIndexAtPosition(x, y); - mouse_over_index_ = index; + if (mouse_over_index_ != index) + { + selected_index_ = mouse_over_index_ = index; + nux::GetWindowCompositor().SetKeyFocusArea(this); + } mouse_last_x_ = x; mouse_last_y_ = y; diff --git a/plugins/unityshell/src/ResultViewGrid.h b/plugins/unityshell/src/ResultViewGrid.h index 891a61392..a814679a5 100644 --- a/plugins/unityshell/src/ResultViewGrid.h +++ b/plugins/unityshell/src/ResultViewGrid.h @@ -87,7 +87,7 @@ private: void PositionPreview(); uint GetIndexAtPosition(int x, int y); - int mouse_over_index_; + uint mouse_over_index_; int active_index_; int selected_index_; uint preview_row_; diff --git a/plugins/unityshell/src/SearchBar.cpp b/plugins/unityshell/src/SearchBar.cpp index 40680a00d..9c7690f28 100644 --- a/plugins/unityshell/src/SearchBar.cpp +++ b/plugins/unityshell/src/SearchBar.cpp @@ -62,19 +62,20 @@ namespace { nux::logging::Logger logger("unity"); - + class ExpanderView : public nux::View { public: ExpanderView(NUX_FILE_LINE_DECL) : nux::View(NUX_FILE_LINE_PARAM) { + SetAcceptKeyNavFocusOnMouseDown(false); + SetAcceptKeyNavFocusOnMouseEnter(true); } protected: void Draw(nux::GraphicsEngine& graphics_engine, bool force_draw) - { - }; + {} void DrawContent(nux::GraphicsEngine& graphics_engine, bool force_draw) { @@ -84,7 +85,17 @@ protected: bool AcceptKeyNavFocus() { - return false; + return true; + } + + nux::Area* FindAreaUnderMouse(const nux::Point& mouse_position, nux::NuxEventType event_type) + { + bool mouse_inside = TestMousePointerInclusionFilterMouseWheel(mouse_position, event_type); + + if (mouse_inside == false) + return nullptr; + + return this; } }; @@ -103,6 +114,7 @@ SearchBar::SearchBar(NUX_FILE_LINE_DECL) , disable_glow(false) , show_filter_hint_(true) , expander_view_(nullptr) + , show_filters_(nullptr) , search_bar_width_(642) , live_search_timeout_(0) , start_spinner_timeout_(0) @@ -118,6 +130,7 @@ SearchBar::SearchBar(int search_bar_width, bool show_filter_hint_, NUX_FILE_LINE , disable_glow(false) , show_filter_hint_(show_filter_hint_) , expander_view_(nullptr) + , show_filters_(nullptr) , search_bar_width_(search_bar_width) , live_search_timeout_(0) , start_spinner_timeout_(0) @@ -162,7 +175,7 @@ void SearchBar::Init() hint_->SetMaximumWidth(search_bar_width_ - icon->GetWidth()); pango_entry_ = new IMTextEntry(); - pango_entry_->sigTextChanged.connect(sigc::mem_fun(this, &SearchBar::OnSearchChanged)); + pango_entry_->text_changed.connect(sigc::mem_fun(this, &SearchBar::OnSearchChanged)); pango_entry_->activated.connect([&]() { activated.emit(); }); pango_entry_->cursor_moved.connect([&](int i) { QueueDraw(); }); pango_entry_->mouse_down.connect(sigc::mem_fun(this, &SearchBar::OnMouseButtonDown)); @@ -218,27 +231,28 @@ void SearchBar::Init() layout_->AddView(expander_view_, 0, nux::MINOR_POSITION_RIGHT, nux::MINOR_SIZE_FULL); // Lambda functions - auto mouse_redraw = [&](int x, int y, unsigned long b, unsigned long k) + auto mouse_expand = [&](int, int, unsigned long, unsigned long) + { + showing_filters = !showing_filters; + }; + + auto key_redraw = [&](nux::Area*, bool, nux::KeyNavDirection) { QueueDraw(); }; - auto mouse_expand = [&](int x, int y, unsigned long b, unsigned long k) + auto key_expand = [&](nux::Area*) { showing_filters = !showing_filters; }; // Signals expander_view_->mouse_click.connect(mouse_expand); - expander_view_->mouse_enter.connect(mouse_redraw); - expander_view_->mouse_leave.connect(mouse_redraw); + expander_view_->key_nav_focus_change.connect(key_redraw); + expander_view_->key_nav_focus_activate.connect(key_expand); show_filters_->mouse_click.connect(mouse_expand); - show_filters_->mouse_enter.connect(mouse_redraw); - show_filters_->mouse_leave.connect(mouse_redraw); expand_icon_->mouse_click.connect(mouse_expand); - expand_icon_->mouse_enter.connect(mouse_redraw); - expand_icon_->mouse_leave.connect(mouse_redraw); - } + } sig_manager_.Add(new Signal<void, GtkSettings*, GParamSpec*> (gtk_settings_get_default(), @@ -406,7 +420,7 @@ void SearchBar::Draw(nux::GraphicsEngine& GfxContext, bool force_draw) geo.y -= (HIGHLIGHT_HEIGHT- geo.height) / 2; geo.height = HIGHLIGHT_HEIGHT; geo.width = HIGHLIGHT_WIDTH + HIGHLIGHT_LEFT_PADDING + HIGHLIGHT_RIGHT_PADDING; - geo.x = geo_arrow.x + (geo_arrow.width - 1) - geo.width + HIGHLIGHT_RIGHT_PADDING; + geo.x = geo_arrow.x + (geo_arrow.width - 1) - geo.width + HIGHLIGHT_RIGHT_PADDING; if (!highlight_layer_) highlight_layer_.reset(dash::Style::Instance().FocusOverlay(geo.width, geo.height)); @@ -582,6 +596,11 @@ nux::TextEntry* SearchBar::text_entry() const return pango_entry_; } +nux::View* SearchBar::show_filters() const +{ + return expander_view_; +} + std::string SearchBar::get_search_string() const { return pango_entry_->GetText(); @@ -612,9 +631,7 @@ bool SearchBar::get_im_active() const // bool SearchBar::ShouldBeHighlighted() { - return ((expander_view_ && expander_view_->IsVisible() && expander_view_->IsMouseInside()) || - (show_filters_ && show_filters_->IsVisible() && show_filters_->IsMouseInside()) || - (expand_icon_ && expand_icon_->IsVisible() && expand_icon_->IsMouseInside())); + return ((expander_view_ && expander_view_->IsVisible() && expander_view_->HasKeyFocus())); } // @@ -635,11 +652,16 @@ std::string SearchBar::GetName() const void SearchBar::AddProperties(GVariantBuilder* builder) { - unity::variant::BuilderWrapper wrapper(builder); - - wrapper.add(GetAbsoluteGeometry()); - wrapper.add("has_focus", pango_entry_->HasKeyFocus()); - wrapper.add("search_string", pango_entry_->GetText()); + unity::variant::BuilderWrapper(builder) + .add(GetAbsoluteGeometry()) + .add("has_focus", pango_entry_->HasKeyFocus()) + .add("search_string", pango_entry_->GetText()) + .add("expander-has-focus", expander_view_->HasKeyFocus()) + .add("showing-filters", showing_filters) + .add("filter-label-x", show_filters_->GetAbsoluteX()) + .add("filter-label-y", show_filters_->GetAbsoluteY()) + .add("filter-label-width", show_filters_->GetAbsoluteWidth()) + .add("filter-label-height", show_filters_->GetAbsoluteHeight()); } } // namespace unity diff --git a/plugins/unityshell/src/SearchBar.h b/plugins/unityshell/src/SearchBar.h index 923925a1e..fc65d0fc2 100644 --- a/plugins/unityshell/src/SearchBar.h +++ b/plugins/unityshell/src/SearchBar.h @@ -63,6 +63,7 @@ public: void SearchFinished(); nux::TextEntry* text_entry() const; + nux::View* show_filters() const; nux::RWProperty<std::string> search_string; nux::Property<std::string> search_hint; diff --git a/plugins/unityshell/src/SearchBarSpinner.cpp b/plugins/unityshell/src/SearchBarSpinner.cpp index 66cdbca63..e55d18a08 100644 --- a/plugins/unityshell/src/SearchBarSpinner.cpp +++ b/plugins/unityshell/src/SearchBarSpinner.cpp @@ -20,6 +20,7 @@ #include "SearchBarSpinner.h" #include <Nux/VLayout.h> +#include <UnityCore/Variant.h> #include "DashStyle.h" @@ -39,9 +40,7 @@ SearchBarSpinner::SearchBarSpinner() _magnify = style.GetSearchMagnifyIcon(); _close = style.GetSearchCloseIcon(); - _close_glow = style.GetSearchCloseGlowIcon(); _spin = style.GetSearchSpinIcon(); - _spin_glow = style.GetSearchSpinGlowIcon(); _2d_rotate.Identity(); _2d_rotate.Rotate_z(0.0); @@ -71,14 +70,6 @@ SearchBarSpinner::Draw(nux::GraphicsEngine& GfxContext, bool force_draw) texxform.min_filter = nux::TEXFILTER_LINEAR; texxform.mag_filter = nux::TEXFILTER_LINEAR; - GfxContext.QRP_1Tex(geo.x + ((geo.width - _spin_glow->GetWidth()) / 2), - geo.y + ((geo.height - _spin_glow->GetHeight()) / 2), - _spin_glow->GetWidth(), - _spin_glow->GetHeight(), - _spin_glow->GetDeviceTexture(), - texxform, - nux::color::White); - if (_state == STATE_READY) { GfxContext.QRP_1Tex(geo.x + ((geo.width - _magnify->GetWidth()) / 2), @@ -137,14 +128,6 @@ SearchBarSpinner::Draw(nux::GraphicsEngine& GfxContext, bool force_draw) nux::color::White); - GfxContext.QRP_1Tex(geo.x + ((geo.width - _close_glow->GetWidth()) / 2), - geo.y + ((geo.height - _close_glow->GetHeight()) / 2), - _close_glow->GetWidth(), - _close_glow->GetHeight(), - _close_glow->GetDeviceTexture(), - texxform, - nux::color::White); - GfxContext.QRP_1Tex(geo.x + ((geo.width - _close->GetWidth()) / 2), geo.y + ((geo.height - _close->GetHeight()) / 2), _close->GetWidth(), @@ -196,7 +179,7 @@ SearchBarSpinner::SetState(SpinnerState state) if (_spinner_timeout) g_source_remove(_spinner_timeout); _spinner_timeout = 0; - + _2d_rotate.Rotate_z(0.0f); _rotation = 0.0f; @@ -218,10 +201,11 @@ void SearchBarSpinner::AddProperties(GVariantBuilder* builder) { nux::Geometry geo = GetGeometry(); - g_variant_builder_add(builder, "{sv}", "x", g_variant_new_int32(geo.x)); - g_variant_builder_add(builder, "{sv}", "y", g_variant_new_int32(geo.y)); - g_variant_builder_add(builder, "{sv}", "width", g_variant_new_int32(geo.width)); - g_variant_builder_add(builder, "{sv}", "height", g_variant_new_int32(geo.height)); + variant::BuilderWrapper(builder) + .add("x", geo.x) + .add("y", geo.y) + .add("width", geo.width) + .add("height", geo.height); } // diff --git a/plugins/unityshell/src/ShortcutHintPrivate.cpp b/plugins/unityshell/src/ShortcutHintPrivate.cpp index 7b4cecefa..3d4a01d8b 100644 --- a/plugins/unityshell/src/ShortcutHintPrivate.cpp +++ b/plugins/unityshell/src/ShortcutHintPrivate.cpp @@ -16,6 +16,8 @@ * Authored by: Andrea Azzarone <azzaronea@gmail.com> */ +#include <glib/gi18n-lib.h> + #include "ShortcutHintPrivate.h" #include <boost/algorithm/string/replace.hpp> @@ -44,9 +46,9 @@ std::string FixMouseShortcut(std::string const& scut) { std::string ret(scut); - boost::replace_all(ret, "Button1", "Left Mouse"); - boost::replace_all(ret, "Button2", "Middle Mouse"); - boost::replace_all(ret, "Button3", "Right Mouse"); + boost::replace_all(ret, "Button1", _("Left Mouse")); + boost::replace_all(ret, "Button2", _("Middle Mouse")); + boost::replace_all(ret, "Button3", _("Right Mouse")); return ret; } diff --git a/plugins/unityshell/src/StaticCairoText.cpp b/plugins/unityshell/src/StaticCairoText.cpp index 23fe8ca3d..4991b3d2f 100644 --- a/plugins/unityshell/src/StaticCairoText.cpp +++ b/plugins/unityshell/src/StaticCairoText.cpp @@ -39,7 +39,7 @@ namespace nux { NUX_IMPLEMENT_OBJECT_TYPE (StaticCairoText); -StaticCairoText::StaticCairoText(const TCHAR* text, +StaticCairoText::StaticCairoText(std::string const& text, NUX_FILE_LINE_DECL) : View(NUX_FILE_LINE_PARAM), _fontstring(NULL), @@ -50,7 +50,7 @@ StaticCairoText::StaticCairoText(const TCHAR* text, { _textColor = Color(1.0f, 1.0f, 1.0f, 1.0f); - _text = TEXT(text); + _text = text; _texture2D = 0; _need_new_extent_cache = true; _pre_layout_width = 0; @@ -223,7 +223,7 @@ StaticCairoText::PostDraw(GraphicsEngine& gfxContext, } void -StaticCairoText::SetText(NString const& text) +StaticCairoText::SetText(std::string const& text) { if (_text != text) { @@ -237,8 +237,8 @@ StaticCairoText::SetText(NString const& text) } } -NString -StaticCairoText::GetText() +std::string +StaticCairoText::GetText() const { return _text; } @@ -324,7 +324,7 @@ void StaticCairoText::GetTextExtents(const TCHAR* font, surface = cairo_image_surface_create(CAIRO_FORMAT_A1, 1, 1); cr = cairo_create(surface); cairo_set_font_options(cr, gdk_screen_get_font_options(screen)); - + layout = pango_cairo_create_layout(cr); desc = pango_font_description_from_string(font); pango_layout_set_font_description(layout, desc); @@ -346,7 +346,7 @@ void StaticCairoText::GetTextExtents(const TCHAR* font, else pango_layout_set_alignment(layout, PANGO_ALIGN_RIGHT); - pango_layout_set_markup(layout, _text.GetTCharPtr(), -1); + pango_layout_set_markup(layout, _text.c_str(), -1); pango_layout_set_height(layout, _lines); pango_layout_set_width(layout, maxwidth * PANGO_SCALE); @@ -420,7 +420,7 @@ void StaticCairoText::DrawText(cairo_t* cr, else pango_layout_set_alignment(layout, PANGO_ALIGN_RIGHT); - pango_layout_set_markup(layout, _text.GetTCharPtr(), -1); + pango_layout_set_markup(layout, _text.c_str(), -1); pango_layout_set_width(layout, width * PANGO_SCALE); pango_layout_set_height(layout, height * PANGO_SCALE); diff --git a/plugins/unityshell/src/StaticCairoText.h b/plugins/unityshell/src/StaticCairoText.h index b39103d3b..9fda4bc79 100644 --- a/plugins/unityshell/src/StaticCairoText.h +++ b/plugins/unityshell/src/StaticCairoText.h @@ -59,7 +59,7 @@ public: NUX_ALIGN_BOTTOM = NUX_ALIGN_RIGHT } AlignState; - StaticCairoText(const TCHAR* text, NUX_FILE_LINE_PROTO); + StaticCairoText(std::string const& text, NUX_FILE_LINE_PROTO); ~StaticCairoText(); @@ -77,7 +77,7 @@ public: bool forceDraw); // public API - void SetText(NString const& text); + void SetText(std::string const& text); void SetTextColor(Color const& textColor); void SetTextEllipsize(EllipsizeState state); void SetTextAlignment(AlignState state); @@ -85,7 +85,7 @@ public: void SetFont(const char* fontstring); void SetLines(int maximum_lines); - NString GetText(); + std::string GetText() const; int GetLineCount(); @@ -108,7 +108,7 @@ private: int _cached_base_width; int _cached_base_height; - NString _text; + std::string _text; Color _textColor; EllipsizeState _ellipsize; AlignState _align; diff --git a/plugins/unityshell/src/SwitcherController.cpp b/plugins/unityshell/src/SwitcherController.cpp index 88ff49f22..21b84f5e9 100644 --- a/plugins/unityshell/src/SwitcherController.cpp +++ b/plugins/unityshell/src/SwitcherController.cpp @@ -42,7 +42,7 @@ Controller::Controller() , show_timer_(0) , detail_timer_(0) { - timeout_length = 150; + timeout_length = 75; detail_on_timeout = true; detail_timeout_length = 1500; monitor_ = 0; @@ -265,7 +265,7 @@ void Controller::Next() switch (detail_mode_) { case TAB_NEXT_WINDOW: - if (model_->detail_selection_index < model_->Selection()->Windows().size () - 1) + if (model_->detail_selection_index < model_->DetailXids().size () - 1) model_->NextDetail(); else model_->Next(); @@ -320,10 +320,10 @@ SwitcherView* Controller::GetView() void Controller::SetDetail(bool value, unsigned int min_windows) { - if (value && model_->Selection()->Windows().size () >= min_windows) + if (value && model_->DetailXids().size () >= min_windows) { model_->detail_selection = true; - detail_mode_ = TAB_NEXT_WINDOW_LOOP; + detail_mode_ = TAB_NEXT_WINDOW; } else { diff --git a/plugins/unityshell/src/Tooltip.cpp b/plugins/unityshell/src/Tooltip.cpp index 74abc4a78..4d3786e70 100644 --- a/plugins/unityshell/src/Tooltip.cpp +++ b/plugins/unityshell/src/Tooltip.cpp @@ -21,6 +21,7 @@ */ #include <Nux/Nux.h> +#include <UnityCore/Variant.h> #include "CairoTexture.h" #include "ubus-server.h" @@ -62,7 +63,7 @@ Tooltip::Tooltip() : _vlayout->AddLayout(_top_space, 0); - _tooltip_text = new nux::StaticCairoText(_labelText.GetTCharPtr(), NUX_TRACKER_LOCATION); + _tooltip_text = new nux::StaticCairoText(_labelText, NUX_TRACKER_LOCATION); _tooltip_text->SetTextAlignment(nux::StaticCairoText::AlignState::NUX_ALIGN_CENTRE); _tooltip_text->SetTextVerticalAlignment(nux::StaticCairoText::AlignState::NUX_ALIGN_CENTRE); _tooltip_text->SetMinimumWidth(MINIMUM_TEXT_WIDTH); @@ -528,7 +529,7 @@ void Tooltip::NotifyConfigurationChange(int width, { } -void Tooltip::SetText(nux::NString const& text) +void Tooltip::SetText(std::string const& text) { if (_labelText == text) return; @@ -548,12 +549,13 @@ std::string Tooltip::GetName() const void Tooltip::AddProperties(GVariantBuilder* builder) { - g_variant_builder_add(builder, "{sv}", "text", g_variant_new_string(_labelText.GetTCharPtr())); - g_variant_builder_add(builder, "{sv}", "x", g_variant_new_int32(GetBaseX())); - g_variant_builder_add(builder, "{sv}", "y", g_variant_new_int32(GetBaseY())); - g_variant_builder_add(builder, "{sv}", "width", g_variant_new_int32(GetBaseWidth())); - g_variant_builder_add(builder, "{sv}", "height", g_variant_new_int32(GetBaseHeight())); - g_variant_builder_add(builder, "{sv}", "active", g_variant_new_boolean(IsVisible())); + variant::BuilderWrapper(builder) + .add("text", _labelText) + .add("x", GetBaseX()) + .add("y", GetBaseY()) + .add("width", GetBaseWidth()) + .add("height", GetBaseHeight()) + .add("active", IsVisible()); } } // namespace nux diff --git a/plugins/unityshell/src/Tooltip.h b/plugins/unityshell/src/Tooltip.h index 3c6433830..ab62689d5 100644 --- a/plugins/unityshell/src/Tooltip.h +++ b/plugins/unityshell/src/Tooltip.h @@ -42,7 +42,7 @@ public: void Draw(nux::GraphicsEngine& gfxContext, bool forceDraw); void DrawContent(nux::GraphicsEngine& gfxContext, bool forceDraw); - void SetText(nux::NString const& text); + void SetText(std::string const& text); void ShowTooltipWithTipAt(int anchor_tip_x, int anchor_tip_y); @@ -69,7 +69,7 @@ private: int _anchorX; int _anchorY; - nux::NString _labelText; + std::string _labelText; nux::ObjectPtr<nux::StaticCairoText> _tooltip_text; diff --git a/plugins/unityshell/src/unity-sctext-accessible.cpp b/plugins/unityshell/src/unity-sctext-accessible.cpp index 6dadce0f1..a900c5caa 100644 --- a/plugins/unityshell/src/unity-sctext-accessible.cpp +++ b/plugins/unityshell/src/unity-sctext-accessible.cpp @@ -145,7 +145,7 @@ unity_sctext_accessible_get_name(AtkObject* obj) text = dynamic_cast<nux::StaticCairoText*>(nux_object_accessible_get_object(NUX_OBJECT_ACCESSIBLE(obj))); if (text != NULL) { - name = text->GetText().GetTCharPtr(); + name = text->GetText().c_str(); pango_parse_markup(name, -1, 0, NULL, &self->priv->stripped_name, NULL, NULL); diff --git a/plugins/unityshell/src/unityshell.cpp b/plugins/unityshell/src/unityshell.cpp index 2f4bba8f9..b7b119acd 100644 --- a/plugins/unityshell/src/unityshell.cpp +++ b/plugins/unityshell/src/unityshell.cpp @@ -89,7 +89,12 @@ void capture_g_log_calls(const gchar* log_domain, gboolean is_extension_supported(const gchar* extensions, const gchar* extension); gfloat get_opengl_version_f32(const gchar* version_string); -} +namespace local +{ +// Tap duration in milliseconds. +const int ALT_TAP_DURATION = 250; +} // namespace local +} // anon namespace UnityScreen::UnityScreen(CompScreen* screen) : BaseSwitchScreen (screen) @@ -106,6 +111,7 @@ UnityScreen::UnityScreen(CompScreen* screen) , _in_paint(false) , relayoutSourceId(0) , _redraw_handle(0) + , alt_tap_timeout_id_(0) , newFocusedWindow(nullptr) , doShellRepaint(false) , allowWindowPaint(false) @@ -291,8 +297,11 @@ UnityScreen::UnityScreen(CompScreen* screen) optionSetAltTabTimeoutNotify(boost::bind(&UnityScreen::optionChanged, this, _1, _2)); optionSetAltTabBiasViewportNotify(boost::bind(&UnityScreen::optionChanged, this, _1, _2)); + optionSetAltTabForwardAllInitiate(boost::bind(&UnityScreen::altTabForwardAllInitiate, this, _1, _2, _3)); optionSetAltTabForwardInitiate(boost::bind(&UnityScreen::altTabForwardInitiate, this, _1, _2, _3)); optionSetAltTabForwardTerminate(boost::bind(&UnityScreen::altTabTerminateCommon, this, _1, _2, _3)); + optionSetAltTabForwardAllTerminate(boost::bind(&UnityScreen::altTabTerminateCommon, this, _1, _2, _3)); + optionSetAltTabPrevAllInitiate(boost::bind(&UnityScreen::altTabPrevAllInitiate, this, _1, _2, _3)); optionSetAltTabPrevInitiate(boost::bind(&UnityScreen::altTabPrevInitiate, this, _1, _2, _3)); optionSetAltTabDetailStartInitiate(boost::bind(&UnityScreen::altTabDetailStartInitiate, this, _1, _2, _3)); @@ -304,7 +313,15 @@ UnityScreen::UnityScreen(CompScreen* screen) optionSetAltTabPrevWindowInitiate(boost::bind(&UnityScreen::altTabPrevWindowInitiate, this, _1, _2, _3)); optionSetAltTabLeftInitiate(boost::bind (&UnityScreen::altTabPrevInitiate, this, _1, _2, _3)); - optionSetAltTabRightInitiate(boost::bind (&UnityScreen::altTabForwardInitiate, this, _1, _2, _3)); + optionSetAltTabRightInitiate([&](CompAction* action, CompAction::State state, CompOption::Vector& options) -> bool + { + if (switcher_controller_->Visible()) + { + switcher_controller_->Next(); + return true; + } + return false; + }); optionSetLauncherSwitcherForwardInitiate(boost::bind(&UnityScreen::launcherSwitcherForwardInitiate, this, _1, _2, _3)); optionSetLauncherSwitcherPrevInitiate(boost::bind(&UnityScreen::launcherSwitcherPrevInitiate, this, _1, _2, _3)); @@ -355,6 +372,9 @@ UnityScreen::~UnityScreen() if (relayoutSourceId != 0) g_source_remove(relayoutSourceId); + if (alt_tap_timeout_id_) + g_source_remove(alt_tap_timeout_id_); + ::unity::ui::IconRenderer::DestroyTextures(); QuicklistManager::Destroy(); @@ -1346,6 +1366,12 @@ void UnityScreen::handleEvent(XEvent* event) skip_other_plugins = launcher_controller_->HandleLauncherKeyEvent(screen->dpy(), key_sym, event->xkey.keycode, event->xkey.state, key_string); if (!skip_other_plugins) skip_other_plugins = dash_controller_->CheckShortcutActivation(key_string); + + if (skip_other_plugins && launcher_controller_->KeyNavIsActive()) + { + launcher_controller_->KeyNavTerminate(false); + EnableCancelAction(false); + } } } break; @@ -1488,7 +1514,7 @@ bool UnityScreen::showLauncherKeyInitiate(CompAction* action, shortcut_controller_->Show(); } - return false; + return true; } bool UnityScreen::showLauncherKeyTerminate(CompAction* action, @@ -1498,17 +1524,17 @@ bool UnityScreen::showLauncherKeyTerminate(CompAction* action, if (state & CompAction::StateCancel) return false; - bool accept_state = (state & CompAction::StateCancel) == 0; + bool was_tap = state & CompAction::StateTermTapped; super_keypressed_ = false; - launcher_controller_->KeyNavTerminate(accept_state); - launcher_controller_->HandleLauncherKeyRelease(); + launcher_controller_->KeyNavTerminate(true); + launcher_controller_->HandleLauncherKeyRelease(was_tap); EnableCancelAction(false); shortcut_controller_->SetEnabled(enable_shortcut_overlay_); shortcut_controller_->Hide(); action->setState (action->state() & (unsigned)~(CompAction::StateTermKey)); - return false; + return true; } bool UnityScreen::showPanelFirstMenuKeyInitiate(CompAction* action, @@ -1519,7 +1545,7 @@ bool UnityScreen::showPanelFirstMenuKeyInitiate(CompAction* action, // to receive the Terminate event action->setState(action->state() | CompAction::StateTermKey); panel_controller_->StartFirstMenuShow(); - return false; + return true; } bool UnityScreen::showPanelFirstMenuKeyTerminate(CompAction* action, @@ -1529,7 +1555,7 @@ bool UnityScreen::showPanelFirstMenuKeyTerminate(CompAction* action, screen->removeGrab(grab_index_, NULL); action->setState (action->state() & (unsigned)~(CompAction::StateTermKey)); panel_controller_->EndFirstMenuShow(); - return false; + return true; } void UnityScreen::SendExecuteCommand() @@ -1543,7 +1569,7 @@ bool UnityScreen::executeCommand(CompAction* action, CompOption::Vector& options) { SendExecuteCommand(); - return false; + return true; } void UnityScreen::startLauncherKeyNav() @@ -1572,18 +1598,22 @@ bool UnityScreen::setKeyboardFocusKeyInitiate(CompAction* action, CompOption::Vector& options) { _key_nav_mode_requested = true; - return false; + return true; } -bool UnityScreen::altTabInitiateCommon(CompAction *action, - CompAction::State state, - CompOption::Vector& options) +bool UnityScreen::altTabInitiateCommon(switcher::ShowMode show_mode) { if (!grab_index_) grab_index_ = screen->pushGrab (screen->invisibleCursor(), "unity-switcher"); if (!grab_index_) return false; - + + if (alt_tap_timeout_id_) + { + g_source_remove(alt_tap_timeout_id_); + alt_tap_timeout_id_ = 0; + } + screen->addAction(&optionGetAltTabRight()); screen->addAction(&optionGetAltTabDetailStart()); screen->addAction(&optionGetAltTabDetailStop()); @@ -1597,13 +1627,17 @@ bool UnityScreen::altTabInitiateCommon(CompAction *action, screen->outputDevs()[device].width() - 200, screen->outputDevs()[device].height() - 200), device); - switcher::ShowMode show_mode = optionGetAltTabBiasViewport() ? switcher::ShowMode::CURRENT_VIEWPORT : switcher::ShowMode::ALL; + if (!optionGetAltTabBiasViewport()) + { + if (show_mode == switcher::ShowMode::CURRENT_VIEWPORT) + show_mode = switcher::ShowMode::ALL; + else + show_mode = switcher::ShowMode::CURRENT_VIEWPORT; + } RaiseInputWindows(); - int show_monitor = (show_mode == switcher::ShowMode::CURRENT_VIEWPORT) ? device : -1; - - auto results = launcher_controller_->GetAltTabIcons(show_monitor); + auto results = launcher_controller_->GetAltTabIcons(show_mode == switcher::ShowMode::CURRENT_VIEWPORT); if (!(results.size() == 1 && results[0]->GetIconType() == AbstractLauncherIcon::IconType::TYPE_BEGIN)) switcher_controller_->Show(show_mode, switcher::SortMode::FOCUS_ORDER, false, results); @@ -1641,19 +1675,39 @@ bool UnityScreen::altTabForwardInitiate(CompAction* action, if (switcher_controller_->Visible()) switcher_controller_->Next(); else - altTabInitiateCommon(action, state, options); + altTabInitiateCommon(switcher::ShowMode::CURRENT_VIEWPORT); action->setState(action->state() | CompAction::StateTermKey); - return false; + return true; +} + +bool UnityScreen::altTabForwardAllInitiate(CompAction* action, + CompAction::State state, + CompOption::Vector& options) +{ + if (switcher_controller_->Visible()) + switcher_controller_->Next(); + else + altTabInitiateCommon(switcher::ShowMode::ALL); + + action->setState(action->state() | CompAction::StateTermKey); + return true; } +bool UnityScreen::altTabPrevAllInitiate(CompAction* action, CompAction::State state, CompOption::Vector& options) +{ + if (switcher_controller_->Visible()) + switcher_controller_->Prev(); + + return true; +} bool UnityScreen::altTabPrevInitiate(CompAction* action, CompAction::State state, CompOption::Vector& options) { if (switcher_controller_->Visible()) switcher_controller_->Prev(); - return false; + return true; } bool UnityScreen::altTabDetailStartInitiate(CompAction* action, CompAction::State state, CompOption::Vector& options) @@ -1661,7 +1715,7 @@ bool UnityScreen::altTabDetailStartInitiate(CompAction* action, CompAction::Stat if (switcher_controller_->Visible()) switcher_controller_->SetDetail(true); - return false; + return true; } bool UnityScreen::altTabDetailStopInitiate(CompAction* action, CompAction::State state, CompOption::Vector& options) @@ -1669,21 +1723,21 @@ bool UnityScreen::altTabDetailStopInitiate(CompAction* action, CompAction::State if (switcher_controller_->Visible()) switcher_controller_->SetDetail(false); - return false; + return true; } bool UnityScreen::altTabNextWindowInitiate(CompAction* action, CompAction::State state, CompOption::Vector& options) { if (!switcher_controller_->Visible()) { - altTabInitiateCommon(action, state, options); + altTabInitiateCommon(switcher::ShowMode::CURRENT_VIEWPORT); switcher_controller_->Select(1); // always select the current application } switcher_controller_->NextDetail(); action->setState(action->state() | CompAction::StateTermKey); - return false; + return true; } bool UnityScreen::altTabPrevWindowInitiate(CompAction* action, CompAction::State state, CompOption::Vector& options) @@ -1691,7 +1745,7 @@ bool UnityScreen::altTabPrevWindowInitiate(CompAction* action, CompAction::State if (switcher_controller_->Visible()) switcher_controller_->PrevDetail(); - return false; + return true; } bool UnityScreen::launcherSwitcherForwardInitiate(CompAction* action, CompAction::State state, CompOption::Vector& options) @@ -1707,13 +1761,13 @@ bool UnityScreen::launcherSwitcherForwardInitiate(CompAction* action, CompAction } action->setState(action->state() | CompAction::StateTermKey); - return false; + return true; } bool UnityScreen::launcherSwitcherPrevInitiate(CompAction* action, CompAction::State state, CompOption::Vector& options) { launcher_controller_->KeyNavPrevious(); - return false; + return true; } bool UnityScreen::launcherSwitcherTerminate(CompAction* action, CompAction::State state, CompOption::Vector& options) { @@ -1722,7 +1776,7 @@ bool UnityScreen::launcherSwitcherTerminate(CompAction* action, CompAction::Stat EnableCancelAction(false); action->setState (action->state() & (unsigned)~(CompAction::StateTermKey)); - return false; + return true; } void UnityScreen::OnLauncherStartKeyNav(GVariant* data) @@ -1745,42 +1799,76 @@ void UnityScreen::OnLauncherEndKeyNav(GVariant* data) PluginAdapter::Default ()->restoreInputFocus (); } -bool UnityScreen::ShowHudInitiate(CompAction* action, CompAction::State state, CompOption::Vector& options) +bool UnityScreen::ShowHudInitiate(CompAction* action, + CompAction::State state, + CompOption::Vector& options) { // to receive the Terminate event if (state & CompAction::StateInitKey) - action->setState(action->state() | CompAction::StateTermKey); - + action->setState(action->state() | CompAction::StateTermKey); last_hud_show_time_ = g_get_monotonic_time(); + /* Workaround to fix #943194 */ + alt_tap_timeout_id_ = g_timeout_add(local::ALT_TAP_DURATION, [] (gpointer data) -> gboolean { + auto self = static_cast<UnityScreen*>(data); + + if (!self->switcher_controller_->Visible()) + { + XUngrabKeyboard(self->screen->dpy(), CurrentTime); + } + + self->alt_tap_timeout_id_ = 0; + return FALSE; + }, this); + + // pass key through return false; } -bool UnityScreen::ShowHudTerminate(CompAction* action, CompAction::State state, CompOption::Vector& options) +bool UnityScreen::ShowHudTerminate(CompAction* action, + CompAction::State state, + CompOption::Vector& options) { - if (optionGetShowHud().key().toString() == action->key().toString()) + // Remember StateCancel and StateCommit will be broadcast to all actions + // so we need to verify that we are actually being toggled... + if (!(state & CompAction::StateTermKey)) + return false; + + action->setState(action->state() & ~CompAction::StateTermKey); + + // And only respond to key taps + if (!(state & CompAction::StateTermTapped)) + return false; + + if (alt_tap_timeout_id_) { - if (switcher_controller_->Visible()) - return false; // early exit if the switcher is open + g_source_remove(alt_tap_timeout_id_); + alt_tap_timeout_id_ = 0; + } - gint64 current_time = g_get_monotonic_time(); - if (current_time - last_hud_show_time_ < 150 * 1000) - { - if (hud_controller_->IsVisible()) - { - ubus_manager_.SendMessage(UBUS_HUD_CLOSE_REQUEST); - } - else - { - hud_controller_->ShowHud(); - } - last_hud_show_time_ = 0; - } + gint64 current_time = g_get_monotonic_time(); + if (current_time - last_hud_show_time_ > (local::ALT_TAP_DURATION * 1000)) + { + LOG_DEBUG(logger) << "Tap too long"; + return false; } - action->setState(action->state() & ~CompAction::StateTermKey); + if (switcher_controller_->Visible()) + { + LOG_ERROR(logger) << "this should never happen"; + return false; // early exit if the switcher is open + } - return false; + if (hud_controller_->IsVisible()) + { + ubus_manager_.SendMessage(UBUS_HUD_CLOSE_REQUEST); + } + else + { + hud_controller_->ShowHud(); + } + + return true; } gboolean UnityScreen::initPluginActions(gpointer data) @@ -2582,7 +2670,7 @@ void UnityScreen::InitHints() hints_.push_back(new shortcut::Hint(launcher, "", _(" (Press)"), _("Open Launcher, displays shortcuts."), shortcut::COMPIZ_KEY_OPTION, "unityshell", "show_launcher" )); hints_.push_back(new shortcut::Hint(launcher, "", "", _("Open Launcher keyboard navigation mode."), shortcut::COMPIZ_KEY_OPTION, "unityshell", "keyboard_focus")); - hints_.push_back(new shortcut::Hint(launcher, "", "", _("Switch applications via Launcher."), shortcut::HARDCODED_OPTION, "Super + Tab")); + hints_.push_back(new shortcut::Hint(launcher, "", "", _("Switch applications via Launcher."), shortcut::HARDCODED_OPTION, _("Super + Tab"))); hints_.push_back(new shortcut::Hint(launcher, "", _(" + 1 to 9"), _("Same as clicking on a Launcher icon."), shortcut::COMPIZ_KEY_OPTION, "unityshell", "show_launcher")); hints_.push_back(new shortcut::Hint(launcher, "", _(" + Shift + 1 to 9"), _("Open new window of the app."), shortcut::COMPIZ_KEY_OPTION, "unityshell", "show_launcher")); hints_.push_back(new shortcut::Hint(launcher, "", " + T", _("Open the Trash."), shortcut::COMPIZ_KEY_OPTION, "unityshell", "show_launcher")); @@ -2594,17 +2682,17 @@ void UnityScreen::InitHints() hints_.push_back(new shortcut::Hint(dash, "", " + A", _("Open the Dash App Lens."), shortcut::COMPIZ_KEY_OPTION, "unityshell", "show_launcher")); hints_.push_back(new shortcut::Hint(dash, "", " + F", _("Open the Dash Files Lens."), shortcut::COMPIZ_KEY_OPTION,"unityshell", "show_launcher")); hints_.push_back(new shortcut::Hint(dash, "", " + M", _("Open the Dash Music Lens."), shortcut::COMPIZ_KEY_OPTION, "unityshell", "show_launcher")); - hints_.push_back(new shortcut::Hint(dash, "", "", _("Switches between Lenses."), shortcut::HARDCODED_OPTION, "Ctrl + Tab")); + hints_.push_back(new shortcut::Hint(dash, "", "", _("Switches between Lenses."), shortcut::HARDCODED_OPTION, _("Ctrl + Tab"))); hints_.push_back(new shortcut::Hint(dash, "", "", _("Moves the focus."), shortcut::HARDCODED_OPTION, _("Cursor Keys"))); hints_.push_back(new shortcut::Hint(dash, "", "", _("Open currently focused item."), shortcut::HARDCODED_OPTION, _("Enter & Return"))); hints_.push_back(new shortcut::Hint(dash, "", "", _("'Run Command' mode."), shortcut::COMPIZ_KEY_OPTION, "unityshell", "execute_command")); - // Top Bar - std::string const topbar = _("Top Bar"); + // Menu Bar + std::string const menubar = _("Menu Bar"); - hints_.push_back(new shortcut::Hint(topbar, "", "", _("Reveals application menu."), shortcut::HARDCODED_OPTION, "Alt")); - hints_.push_back(new shortcut::Hint(topbar, "", "", _("Opens the indicator menu."), shortcut::COMPIZ_KEY_OPTION, "unityshell", "panel_first_menu")); - hints_.push_back(new shortcut::Hint(topbar, "", "", _("Moves focus between indicators."), shortcut::HARDCODED_OPTION, _("Cursor Left or Right"))); + hints_.push_back(new shortcut::Hint(menubar, "", "", _("Reveals application menu."), shortcut::HARDCODED_OPTION, "Alt")); + hints_.push_back(new shortcut::Hint(menubar, "", "", _("Opens the indicator menu."), shortcut::COMPIZ_KEY_OPTION, "unityshell", "panel_first_menu")); + hints_.push_back(new shortcut::Hint(menubar, "", "", _("Moves focus between indicators."), shortcut::HARDCODED_OPTION, _("Cursor Left or Right"))); // Switching std::string const switching = _("Switching"); @@ -2625,12 +2713,12 @@ void UnityScreen::InitHints() hints_.push_back(new shortcut::Hint(windows, "", "", _("Minimises all windows."), shortcut::COMPIZ_KEY_OPTION, "core", "show_desktop_key")); hints_.push_back(new shortcut::Hint(windows, "", "", _("Maximises the current window."), shortcut::COMPIZ_KEY_OPTION, "core", "maximize_window_key")); hints_.push_back(new shortcut::Hint(windows, "", "", _("Restores or minimises current window."), shortcut::COMPIZ_KEY_OPTION, "core", "unmaximize_window_key")); - hints_.push_back(new shortcut::Hint(windows, "", " or Right", _("Semi-maximises current window."), shortcut::COMPIZ_KEY_OPTION, "grid", "put_left_key")); + hints_.push_back(new shortcut::Hint(windows, "", _(" or Right"), _("Semi-maximises current window."), shortcut::COMPIZ_KEY_OPTION, "grid", "put_left_key")); hints_.push_back(new shortcut::Hint(windows, "", "", _("Closes current window."), shortcut::COMPIZ_KEY_OPTION, "core", "close_window_key")); - hints_.push_back(new shortcut::Hint(windows, "", "", _("Opens window accessibility menu."), shortcut::HARDCODED_OPTION, "Alt + Space")); - hints_.push_back(new shortcut::Hint(windows, "", "", _("Places window in corresponding positions."), shortcut::HARDCODED_OPTION, "Ctrl + Alt + Num")); - hints_.push_back(new shortcut::Hint(windows, "", " Drag", _("Move window."), shortcut::COMPIZ_MOUSE_OPTION, "move", "initiate_button")); - hints_.push_back(new shortcut::Hint(windows, "", " Drag", _("Resize window."), shortcut::COMPIZ_MOUSE_OPTION, "resize", "initiate_button")); + hints_.push_back(new shortcut::Hint(windows, "", "", _("Opens window accessibility menu."), shortcut::HARDCODED_OPTION, _("Alt + Space"))); + hints_.push_back(new shortcut::Hint(windows, "", "", _("Places window in corresponding positions."), shortcut::HARDCODED_OPTION, _("Ctrl + Alt + Num"))); + hints_.push_back(new shortcut::Hint(windows, "", _(" Drag"), _("Move window."), shortcut::COMPIZ_MOUSE_OPTION, "move", "initiate_button")); + hints_.push_back(new shortcut::Hint(windows, "", _(" Drag"), _("Resize window."), shortcut::COMPIZ_MOUSE_OPTION, "resize", "initiate_button")); } /* Window init */ diff --git a/plugins/unityshell/src/unityshell.h b/plugins/unityshell/src/unityshell.h index 39a73e03f..7f0637bac 100644 --- a/plugins/unityshell/src/unityshell.h +++ b/plugins/unityshell/src/unityshell.h @@ -192,15 +192,15 @@ public: bool executeCommand(CompAction* action, CompAction::State state, CompOption::Vector& options); bool setKeyboardFocusKeyInitiate(CompAction* action, CompAction::State state, CompOption::Vector& options); - bool altTabInitiateCommon(CompAction* action, - CompAction::State state, - CompOption::Vector& options); + bool altTabInitiateCommon(switcher::ShowMode mode); bool altTabTerminateCommon(CompAction* action, CompAction::State state, CompOption::Vector& options); bool altTabForwardInitiate(CompAction* action, CompAction::State state, CompOption::Vector& options); bool altTabPrevInitiate(CompAction* action, CompAction::State state, CompOption::Vector& options); + bool altTabForwardAllInitiate(CompAction* action, CompAction::State state, CompOption::Vector& options); + bool altTabPrevAllInitiate(CompAction* action, CompAction::State state, CompOption::Vector& options); bool altTabDetailStartInitiate(CompAction* action, CompAction::State state, CompOption::Vector& options); bool altTabDetailStopInitiate(CompAction* action, CompAction::State state, CompOption::Vector& options); bool altTabNextWindowInitiate(CompAction* action, CompAction::State state, CompOption::Vector& options); @@ -290,6 +290,7 @@ private: bool _in_paint; guint32 relayoutSourceId; guint32 _redraw_handle; + guint32 alt_tap_timeout_id_; typedef std::shared_ptr<CompAction> CompActionPtr; typedef std::vector<CompActionPtr> ShortcutActions; ShortcutActions _shortcut_actions; diff --git a/plugins/unityshell/unityshell.xml.in b/plugins/unityshell/unityshell.xml.in index d2240e025..88d8ea411 100644 --- a/plugins/unityshell/unityshell.xml.in +++ b/plugins/unityshell/unityshell.xml.in @@ -36,7 +36,6 @@ <plugin>compiztoolbox</plugin> <plugin>scale</plugin> <plugin>expo</plugin> - <feature>largedesktop</feature> </requirement> </deps> <options> @@ -95,7 +94,7 @@ <option name="panel_first_menu" type="key"> <_short>Key to open the first panel menu</_short> <_long>Open the first menu on the panel, allowing keyboard navigation thereafter.</_long> - <default>F10</default> + <default><Alt>F10</default> </option> <option name="launcher_switcher_forward" type="key"> <_short>Key to start the launcher application switcher</_short> @@ -130,20 +129,30 @@ <_long>fixme</_long> <default><Alt><Shift>Tab</default> </option> - <option name="alt_tab_right" type="key"> - <_short>Go right in the switcher</_short> - <_long>fixme</_long> - <default><Alt>Right</default> - <passive_grab>false</passive_grab> - <internal/> - </option> - <option name="alt_tab_left" type="key"> - <_short>Go left in the switcher</_short> - <_long>fixme</_long> - <default><Alt>Left</default> - <passive_grab>false</passive_grab> - <internal/> - </option> + <option name="alt_tab_forward_all" type="key"> + <_short>Key to start the switcher for all viewports</_short> + <_long>fixme</_long> + <default><Control><Alt>Tab</default> + </option> + <option name="alt_tab_prev_all" type="key"> + <_short>Key to start the switcher in reverse for all viewports</_short> + <_long>fixme</_long> + <default><Control><Alt><Shift>Tab</default> + </option> + <option name="alt_tab_right" type="key"> + <_short>Go right in the switcher</_short> + <_long>fixme</_long> + <default><Alt>Right</default> + <passive_grab>false</passive_grab> + <internal/> + </option> + <option name="alt_tab_left" type="key"> + <_short>Go left in the switcher</_short> + <_long>fixme</_long> + <default><Alt>Left</default> + <passive_grab>false</passive_grab> + <internal/> + </option> <option name="alt_tab_detail_start" type="key"> <_short>Key to expose the windows in the switcher</_short> <_long>fixme</_long> @@ -371,7 +380,7 @@ <max>100</max> <default>75</default> </option> - + <option name="devices_option" type="int"> <_short>Show Devices</_short> <_long>Show devices in the launcher</_long> diff --git a/po/POTFILES.in b/po/POTFILES.in index 18a88b12e..7850c1942 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -15,6 +15,7 @@ plugins/unityshell/src/FilterMultiRangeWidget.cpp plugins/unityshell/src/FilterRatingsWidget.cpp plugins/unityshell/src/PreviewMusic.cpp plugins/unityshell/src/PreviewMusicTrack.cpp +plugins/unityshell/src/ShortcutHintPrivate.cpp plugins/unityshell/src/unity-launcher-accessible.cpp plugins/unityshell/src/unity-search-bar-accessible.cpp plugins/unityshell/src/unityshell.cpp diff --git a/services/panel-service.c b/services/panel-service.c index d3a0e59b4..35225d97a 100644 --- a/services/panel-service.c +++ b/services/panel-service.c @@ -99,6 +99,7 @@ static guint32 _service_signals[LAST_SIGNAL] = { 0 }; static const gchar * indicator_order[][2] = { {"libappmenu.so", NULL}, /* indicator-appmenu" */ {"libapplication.so", NULL}, /* indicator-application" */ + {"libprintersmenu.so", NULL}, /* indicator-printers */ {"libapplication.so", "gsd-keyboard-xkb"}, /* keyboard layout selector */ {"libmessaging.so", NULL}, /* indicator-messages */ {"libpower.so", NULL}, /* indicator-power */ diff --git a/standalone-clients/CMakeLists.txt b/standalone-clients/CMakeLists.txt index f8eaf8bcc..ecc4821bd 100644 --- a/standalone-clients/CMakeLists.txt +++ b/standalone-clients/CMakeLists.txt @@ -70,12 +70,11 @@ add_executable (dash ${UNITY_SRC}/FilterGenreButton.cpp ${UNITY_SRC}/FilterGenreWidget.cpp ${UNITY_SRC}/FilterBar.cpp - ${UNITY_SRC}/FilterWidget.cpp - ${UNITY_SRC}/FilterWidget.h ${UNITY_SRC}/FontSettings.cpp ${UNITY_SRC}/FontSettings.h ${UNITY_SRC}/IMTextEntry.cpp ${UNITY_SRC}/IMTextEntry.h + ${UNITY_SRC}/IntrospectableWrappers.cpp ${UNITY_SRC}/PlacesGroup.cpp ${UNITY_SRC}/PlacesGroup.h ${UNITY_SRC}/PlacesTile.cpp @@ -355,7 +354,6 @@ add_executable (filter-bar ${UNITY_SRC}/FilterGenreWidget.cpp ${UNITY_SRC}/FilterRatingsButton.cpp ${UNITY_SRC}/FilterRatingsWidget.cpp - ${UNITY_SRC}/FilterWidget.cpp ${UNITY_SRC}/DashStyle.cpp ${UNITY_SRC}/JSONParser.cpp ) diff --git a/standalone-clients/TestShortcut.cpp b/standalone-clients/TestShortcut.cpp index 8a658c115..319877d3a 100644 --- a/standalone-clients/TestShortcut.cpp +++ b/standalone-clients/TestShortcut.cpp @@ -55,11 +55,11 @@ void ThreadWidgetInit(nux::NThread* thread, void* InitData) hints.push_back(new shortcut::MockHint(_("Dash"), "", "", _("Open currently focused item."), shortcut::HARDCODED_OPTION, _("Enter / Return"))); hints.push_back(new shortcut::MockHint(_("Dash"), "", "", _("'Run Command' mode."), shortcut::COMPIZ_KEY_OPTION, "unityshell", "execute_command")); - // Top Bar + // Menu Bar // Is it really hard coded? - hints.push_back(new shortcut::MockHint(_("Top Bar"), "", "", _("Reveals application menu."), shortcut::HARDCODED_OPTION, "Alt")); - hints.push_back(new shortcut::MockHint(_("Top Bar"), "", "", _("Opens the indicator menu."), shortcut::COMPIZ_KEY_OPTION, "unityshell", "panel_first_menu")); - hints.push_back(new shortcut::MockHint(_("Top Bar"), "", "", _("Moves focus between indicators."), shortcut::HARDCODED_OPTION, _("Cursor Left & Right"))); + hints.push_back(new shortcut::MockHint(_("Menu Bar"), "", "", _("Reveals application menu."), shortcut::HARDCODED_OPTION, "Alt")); + hints.push_back(new shortcut::MockHint(_("Menu Bar"), "", "", _("Opens the indicator menu."), shortcut::COMPIZ_KEY_OPTION, "unityshell", "panel_first_menu")); + hints.push_back(new shortcut::MockHint(_("Menu Bar"), "", "", _("Moves focus between indicators."), shortcut::HARDCODED_OPTION, _("Cursor Left & Right"))); // Switching hints.push_back(new shortcut::MockHint(_("Switching"), "", "", _("Switch between applications."), shortcut::COMPIZ_KEY_OPTION, "unityshell", "alt_tab_forward")); diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 32bc99fac..b458abef5 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -79,18 +79,19 @@ add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/test_glib_signals_utils_ma COMMENT "Generating marshallers") enable_testing() -find_package(GTest) # :( find_library (GMOCK_LIB gmock) find_library (GMOCK_MAIN_LIB gmock_main) +find_path(GTEST_SRC_DIR gtest.cc PATHS /usr/src/gtest/src) -if (GTEST_FOUND AND +if (GTEST_SRC_DIR AND GMOCK_LIB AND GMOCK_MAIN_LIB) - include_directories(${GTEST_INCLUDE_DIRS}) # The service that provides DBus services to test against add_executable(test-gtest-service + test_service_gdbus_wrapper.c + test_service_gdbus_wrapper.h test_service_hud.c test_service_hud.h test_service_lens.c @@ -98,7 +99,7 @@ if (GTEST_FOUND AND test_service_main.c test_service_model.c test_service_model.h) - add_dependencies (test-gtest-service unity-core-${UNITY_API_VERSION}) + add_dependencies (test-gtest-service unity-core-${UNITY_API_VERSION} gtest) # The actual test executable (xless) - do not put anything that requires X in here @@ -165,15 +166,17 @@ if (GTEST_FOUND AND ${CMAKE_SOURCE_DIR}/plugins/unity-mt-grab-handles/src/unity-mt-grab-handle-layout.cpp ${CMAKE_SOURCE_DIR}/plugins/unity-mt-grab-handles/src/unity-mt-texture.cpp ) - target_link_libraries(test-gtest-xless ${GTEST_BOTH_LIBRARIES} ${GMOCK_LIB} ${GMOCK_MAIN_LIB}) + target_link_libraries(test-gtest-xless gtest ${GMOCK_LIB} ${GMOCK_MAIN_LIB}) add_test(UnityGTestXless test-gtest-xless) - add_dependencies(test-gtest-xless unity-core-${UNITY_API_VERSION}) + add_dependencies(test-gtest-xless unity-core-${UNITY_API_VERSION} gtest) # tests that require dbus, must not require X add_executable(test-gtest-dbus test_categories.cpp test_filesystem_lenses.cpp test_filter.cpp + test_gdbus_proxy.cpp + test_hud.cpp test_indicator_entry.cpp test_lens.cpp test_main_dbus.cpp @@ -181,11 +184,10 @@ if (GTEST_FOUND AND test_utils.h test_ratings_filter.cpp test_results.cpp - test_hud.cpp ) - target_link_libraries(test-gtest-dbus ${GTEST_BOTH_LIBRARIES}) + target_link_libraries(test-gtest-dbus gtest) add_test(UnityGTestDBus test-gtest-dbus) - add_dependencies(test-gtest-dbus unity-core-${UNITY_API_VERSION} test-gtest-service) + add_dependencies(test-gtest-dbus unity-core-${UNITY_API_VERSION} test-gtest-service gtest) # Tests that require X add_executable(test-gtest @@ -202,11 +204,11 @@ if (GTEST_FOUND AND ${UNITY_SRC}/Timer.cpp ${UNITY_SRC}/Timer.h ) - target_link_libraries(test-gtest ${GTEST_BOTH_LIBRARIES}) + target_link_libraries(test-gtest gtest) add_test(UnityGTest test-gtest) - add_dependencies(test-gtest unity-core-${UNITY_API_VERSION}) + add_dependencies(test-gtest unity-core-${UNITY_API_VERSION} gtest) -endif (GTEST_FOUND AND +endif (GTEST_SRC_DIR AND GMOCK_LIB AND GMOCK_MAIN_LIB) @@ -231,11 +233,14 @@ set (TEST_COMMAND_HEADLESS #&& ${GTEST_TEST_COMMAND_DBUS} && echo "Warning, DBus test cases are disabled!!") -add_custom_target (check COMMAND ${TEST_COMMAND} DEPENDS test-unit test-gtest test-gtest-xless test-gtest-dbus) -add_custom_target (check-headless COMMAND ${TEST_COMMAND_HEADLESS} DEPENDS test-gtest-xless test-gtest-dbus) -add_custom_target (check-report COMMAND ${TEST_UNIT_COMMAND} && gtester-report ${TEST_RESULT_XML} > ${TEST_RESULT_HTML}) -add_custom_target (gcheck COMMAND ${DBUS_TEST_COMMAND} DEPENDS test-gtest test-gtest-xless) - +if (GTEST_SRC_DIR) + add_custom_target (check COMMAND ${TEST_COMMAND} DEPENDS test-unit test-gtest test-gtest-xless test-gtest-dbus) + add_custom_target (check-headless COMMAND ${TEST_COMMAND_HEADLESS} DEPENDS test-gtest-xless test-gtest-dbus) + add_custom_target (check-report COMMAND ${TEST_UNIT_COMMAND} && gtester-report ${TEST_RESULT_XML} > ${TEST_RESULT_HTML}) + add_custom_target (gcheck COMMAND ${DBUS_TEST_COMMAND} DEPENDS test-gtest test-gtest-xless) +else (GTEST_SRC_DIR) + add_custom_target (check COMMAND ${TEST_COMMAND} DEPENDS test-unit) +endif (GTEST_SRC_DIR) # make target to allow devs to run "make autopilot" from build dir: set (AUTOPILOTDIR "${CMAKE_CURRENT_SOURCE_DIR}/autopilot") # Rules to install autopilot files and executable script: diff --git a/tests/autopilot/autopilot/emulators/X11.py b/tests/autopilot/autopilot/emulators/X11.py index 7513adbf7..5229b83b4 100644 --- a/tests/autopilot/autopilot/emulators/X11.py +++ b/tests/autopilot/autopilot/emulators/X11.py @@ -26,6 +26,7 @@ from Xlib.ext.xtest import fake_input import gtk.gdk _PRESSED_KEYS = [] +_PRESSED_MOUSE_BUTTONS = [] _DISPLAY = Display() logger = logging.getLogger(__name__) @@ -80,6 +81,7 @@ class Keyboard(object): 'Super' : 'Super_L', 'Shift' : 'Shift_L', 'Enter' : 'Return', + 'Space' : ' ', } def __init__(self): @@ -99,8 +101,9 @@ class Keyboard(object): if not isinstance(keys, basestring): raise TypeError("'keys' argument must be a string.") logger.debug("Pressing keys %r with delay %f", keys, delay) - self.__perform_on_keys(self.__translate_keys(keys), X.KeyPress) - sleep(delay) + for key in self.__translate_keys(keys): + self.__perform_on_key(key, X.KeyPress) + sleep(delay) def release(self, keys, delay=0.2): """Send key release events only. @@ -116,8 +119,12 @@ class Keyboard(object): if not isinstance(keys, basestring): raise TypeError("'keys' argument must be a string.") logger.debug("Releasing keys %r with delay %f", keys, delay) - self.__perform_on_keys(self.__translate_keys(keys), X.KeyRelease) - sleep(delay) + # release keys in the reverse order they were pressed in. + keys = self.__translate_keys(keys) + keys.reverse() + for key in keys: + self.__perform_on_key(key, X.KeyRelease) + sleep(delay) def press_and_release(self, keys, delay=0.2): """Press and release all items in 'keys'. @@ -158,26 +165,35 @@ class Keyboard(object): any keys that were pressed and not released. """ + global _PRESSED_KEYS for keycode in _PRESSED_KEYS: logger.warning("Releasing key %r as part of cleanup call.", keycode) fake_input(_DISPLAY, X.KeyRelease, keycode) + _PRESSED_KEYS = [] + + def __perform_on_key(self, key, event): + if not isinstance(key, basestring): + raise TypeError("Key parameter must be a string") - def __perform_on_keys(self, keys, event): keycode = 0 shift_mask = 0 - for key in keys: - keycode, shift_mask = self.__char_to_keycode(key) + keycode, shift_mask = self.__char_to_keycode(key) - if shift_mask != 0: - fake_input(_DISPLAY, event, 50) + if shift_mask != 0: + fake_input(_DISPLAY, event, 50) - if event == X.KeyPress: - _PRESSED_KEYS.append(keycode) - elif event == X.KeyRelease: + if event == X.KeyPress: + logger.debug("Sending press event for key: %s", key) + _PRESSED_KEYS.append(keycode) + elif event == X.KeyRelease: + logger.debug("Sending release event for key: %s", key) + if keycode in _PRESSED_KEYS: _PRESSED_KEYS.remove(keycode) + else: + logger.warning("Generating release event for keycode %d that was not pressed.", keycode) - fake_input(_DISPLAY, event, keycode) + fake_input(_DISPLAY, event, keycode) _DISPLAY.sync() def __get_keysym(self, key) : @@ -224,12 +240,17 @@ class Mouse(object): def press(self, button=1): """Press mouse button at current mouse location.""" logger.debug("Pressing mouse button %d", button) + _PRESSED_MOUSE_BUTTONS.append(button) fake_input(_DISPLAY, X.ButtonPress, button) _DISPLAY.sync() def release(self, button=1): """Releases mouse button at current mouse location.""" logger.debug("Releasing mouse button %d", button) + if button in _PRESSED_MOUSE_BUTTONS: + _PRESSED_MOUSE_BUTTONS.remove(button) + else: + logger.warning("Generating button release event or button %d that was not pressed.", button) fake_input(_DISPLAY, X.ButtonRelease, button) _DISPLAY.sync() @@ -242,7 +263,7 @@ class Mouse(object): def move(self, x, y, animate=True, rate=100, time_between_events=0.001): '''Moves mouse to location (x, y, pixels_per_event, time_between_event)''' logger.debug("Moving mouse to position %d,%d %s animation.", x, y, - "with" if animate else "false") + "with" if animate else "without") def perform_move(x, y, sync): fake_input(_DISPLAY, X.MotionNotify, sync, X.CurrentTime, X.NONE, x=x, y=y) @@ -283,6 +304,11 @@ class Mouse(object): @staticmethod def cleanup(): """Put mouse in a known safe state.""" + global _PRESSED_MOUSE_BUTTONS + for btn in _PRESSED_MOUSE_BUTTONS: + logger.debug("Releasing mouse button %d as part of cleanup", btn) + fake_input(_DISPLAY, X.ButtonRelease, btn) + _PRESSED_MOUSE_BUTTONS = [] sg = ScreenGeometry() sg.move_mouse_to_monitor(0) @@ -297,6 +323,12 @@ class ScreenGeometry: """Get the number of monitors attached to the PC.""" return self._default_screen.get_n_monitors() + def get_screen_width(self): + return self._default_screen.get_width() + + def get_screen_height(self): + return self._default_screen.get_height() + def get_monitor_geometry(self, monitor_number): """Get the geometry for a particular monitor. diff --git a/tests/autopilot/autopilot/emulators/bamf.py b/tests/autopilot/autopilot/emulators/bamf.py index 7e739861c..5b7ab8a83 100644 --- a/tests/autopilot/autopilot/emulators/bamf.py +++ b/tests/autopilot/autopilot/emulators/bamf.py @@ -11,14 +11,16 @@ import dbus import dbus.glib import gio import gobject +import os from Xlib import display, X, protocol from autopilot.emulators.dbus_handler import session_bus -__all__ = ["Bamf", - "BamfApplication", - "BamfWindow", - ] +__all__ = [ + "Bamf", + "BamfApplication", + "BamfWindow", + ] _BAMF_BUS_NAME = 'org.ayatana.bamf' _X_DISPLAY = display.Display() @@ -37,7 +39,7 @@ def _filter_user_visible(win): return False -class Bamf: +class Bamf(object): """High-level class for interacting with Bamf from within a test. Use this class to inspect the state of running applications and open @@ -63,14 +65,14 @@ class Bamf: return filter(_filter_user_visible, apps) return apps - def get_running_applications_by_title(self, app_title): - """Return a list of applications that have the title `app_title`. + def get_running_applications_by_desktop_file(self, desktop_file): + """Return a list of applications that have the desktop file 'desktop_file'`. This method may return an empty list, if no applications - are found with the specified title. + are found with the specified desktop file. """ - return [a for a in self.get_running_applications() if a.name == app_title] + return [a for a in self.get_running_applications() if a.desktop_file == desktop_file] def get_open_windows(self, user_visible_only=True): """Get a list of currently open windows. @@ -85,41 +87,23 @@ class Bamf: return filter(_filter_user_visible, windows) return windows - def get_open_windows_by_title(self, win_title): - """Get a list of all open windows with a specific window title. - - This method may return an empty list if no currently open windows have - the specified title. - - """ - return [w for w in self.get_open_windows() if w.title == win_title] - - def application_is_running(self, app_name): - """Detect if an application with a given name is currently running. - - 'app_name' is the name of the application you are looking for. - """ - try: - return app_name in [a.name for a in self.get_running_applications()] - except dbus.DBusException: - return False - - def wait_until_application_is_running(self, app_name, timeout): + def wait_until_application_is_running(self, desktop_file, timeout): """Wait until a given application is running. - 'app_name' is the name of the application. + 'desktop_file' is the name of the application desktop file. 'timeout' is the maximum time to wait, in seconds. If set to something less than 0, this method will wait forever. This method returns true once the application is found, or false if the application was not found until the timeout was reached. """ + desktop_file = os.path.split(desktop_file)[1] # python workaround since you can't assign to variables in the enclosing scope: # see on_timeout_reached below... found_app = [True] # maybe the app is running already? - if not self.application_is_running(app_name): + if len(self.get_running_applications_by_desktop_file(desktop_file)) == 0: wait_forever = timeout < 0 gobject_loop = gobject.MainLoop() @@ -127,7 +111,7 @@ class Bamf: def on_view_added(bamf_path, name): if bamf_path.split('/')[-1].startswith('application'): app = BamfApplication(bamf_path) - if app.name == app_name: + if desktop_file == os.path.split(app.desktop_file)[1]: gobject_loop.quit() # ...and one for when the user-defined timeout has been reached: @@ -157,11 +141,11 @@ class Bamf: proc = gio.unix.DesktopAppInfo(desktop_file) proc.launch() if wait: - self.wait_until_application_is_running(proc.get_name(), -1) + self.wait_until_application_is_running(desktop_file, -1) return proc -class BamfApplication: +class BamfApplication(object): """Represents an application, with information as returned by Bamf. Don't instantiate this class yourself. instead, use the methods as @@ -173,13 +157,24 @@ class BamfApplication: try: self._app_proxy = session_bus.get_object(_BAMF_BUS_NAME, bamf_app_path) self._view_iface = dbus.Interface(self._app_proxy, 'org.ayatana.bamf.view') + self._app_iface = dbus.Interface(self._app_proxy, 'org.ayatana.bamf.application') except dbus.DBusException, e: e.message += 'bamf_app_path=%r' % (bamf_app_path) raise @property + def desktop_file(self): + """Get the application desktop file""" + return os.path.split(self._app_iface.DesktopFile())[1] + + @property def name(self): - """Get the application name.""" + """Get the application name. + + Note: This may change according to the current locale. If you want a unique + string to match applications against, use the desktop_file instead. + + """ return self._view_iface.Name() @property @@ -210,7 +205,7 @@ class BamfApplication: return "<BamfApplication '%s'>" % (self.name) -class BamfWindow: +class BamfWindow(object): """Represents an application window, as returned by Bamf. Don't instantiate this class yourself. Instead, use the appropriate methods @@ -243,6 +238,8 @@ class BamfWindow: This may be different from the application name. + Note that this may change depending on the current locale. + """ return self._getProperty('_NET_WM_NAME') @@ -302,6 +299,12 @@ class BamfWindow: return '_NET_WM_STATE_HIDDEN' in win_state @property + def is_focused(self): + """Is this window focused?""" + win_state = self._get_window_states() + return '_NET_WM_STATE_FOCUSED' in win_state + + @property def is_valid(self): """Is this window object valid? @@ -347,4 +350,5 @@ class BamfWindow: def _get_window_states(self): """Return a list of strings representing the current window state.""" + _X_DISPLAY.sync() return map(_X_DISPLAY.get_atom_name, self._getProperty('_NET_WM_STATE')) diff --git a/tests/autopilot/autopilot/emulators/ibus.py b/tests/autopilot/autopilot/emulators/ibus.py new file mode 100644 index 000000000..d9cb175ac --- /dev/null +++ b/tests/autopilot/autopilot/emulators/ibus.py @@ -0,0 +1,100 @@ +# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*- +# Copyright 2012 Canonical +# Author: Thomi Richards +# +# This program is free software: you can redistribute it and/or modify it +# under the terms of the GNU General Public License version 3, as published +# by the Free Software Foundation. + +"Functions to deal with ibus service." + +# without this the 'import ibus' imports us, and import ibus.common fails. 0.O +from __future__ import absolute_import +import ibus +import ibus.common +import os +import logging +from time import sleep + +logger = logging.getLogger(__name__) + + +def get_ibus_bus(): + """Get the ibus bus object, possibly starting the ibus daemon if it's + not already running. + + """ + max_tries = 5 + for i in range(max_tries): + if ibus.common.get_address() is None: + pid = os.spawnlp(os.P_NOWAIT, "ibus-daemon", "ibus-daemon", "-d", "--xim") + logger.info("Started ibus-daemon with pid %i." % (pid)) + sleep(2) + else: + return ibus.Bus() + raise RuntimeError("Could not start ibus-daemon after %d tries." % (max_tries)) + + +def get_available_input_engines(): + """Get a list of available input engines.""" + bus = get_ibus_bus() + return [e.name for e in bus.list_engines()] + + +def get_active_input_engines(): + """Get the list of input engines that have been activated.""" + bus = get_ibus_bus() + return [e.name for e in bus.list_active_engines()] + +def set_active_engines(engine_list): + """Installs the engines in 'engine_list' into the list of active iBus engines. + + The specified engines must appear in the return list from + get_available_input_engines(). This function removes all other engines. + + This function returns the list of engines installed before this function was + called. The caller should pass this list to set_active_engines to restore + ibus to it's old state once the test has finished. + """ + if type(engine_list) is not list: + raise TypeError("engine_list must be a list of valid engine names.") + available_engines = get_available_input_engines() + for engine in engine_list: + if not isinstance(engine, basestring): + raise TypeError("Engines in engine_list must all be strings.") + if engine not in available_engines: + raise ValueError("engine_list contains invalid engine name: '%s'", engine) + + bus = get_ibus_bus() + config = bus.get_config() + old_engines = get_active_input_engines() + config.set_list("general", "preload_engines", engine_list, "s") + # need to restart the ibus bus before it'll pick up the new engine. + # see bug report here: + # http://code.google.com/p/ibus/issues/detail?id=1418&thanks=1418&ts=1329885137 + bus.exit(restart=True) + sleep(1) + return old_engines + +def set_global_input_engine(engine_name): + """Set the global iBus input engine by name. + + This function enables the global input engine. To turn it off again, pass None + as the engine name. + + """ + if not (engine_name is None or isinstance(engine_name, basestring)): + raise TypeError("engine_name type must be either str or None.") + + bus = get_ibus_bus() + + if engine_name: + available_engines = get_available_input_engines() + if not engine_name in available_engines: + raise ValueError("Unknown engine '%s'" % (engine_name)) + bus.get_config().set_value("general", "use_global_engine", True) + bus.set_global_engine(engine_name) + logger.info('Enabling global ibus engine "%s".' % (engine_name)) + else: + bus.get_config().set_value("general", "use_global_engine", False) + logger.info('Disabling global ibus engine.') diff --git a/tests/autopilot/autopilot/emulators/unity/__init__.py b/tests/autopilot/autopilot/emulators/unity/__init__.py index 615e29ea4..495c4c0a7 100644 --- a/tests/autopilot/autopilot/emulators/unity/__init__.py +++ b/tests/autopilot/autopilot/emulators/unity/__init__.py @@ -46,7 +46,6 @@ _introspection_iface = Interface(_debug_proxy_obj, INTROSPECTION_IFACE) def get_state_by_path(piece='/Unity'): """Returns a full dump of unity's state.""" - logger.debug("Querying unity for state piece: %r", piece) return _introspection_iface.GetState(piece) @@ -58,7 +57,6 @@ def get_state_by_name_and_id(class_name, unique_id): Returns a dictionary of information. Unlike get_state_by_path, this method can never return state for more than one object. """ - logger.debug("Getting state for object %s with id %d", class_name, unique_id) try: query = "//%(class_name)s[id=%(unique_id)d]" % (dict( class_name=class_name, @@ -87,6 +85,7 @@ class UnityIntrospectionObject(object): __metaclass__ = IntrospectableObjectMetaclass def __init__(self, state_dict): + self.__state = {} self.set_properties(state_dict) def set_properties(self, state_dict): @@ -95,8 +94,12 @@ class UnityIntrospectionObject(object): Translates '-' to '_', so a key of 'icon-type' for example becomes 'icon_type'. """ - for key in state_dict.keys(): - setattr(self, key.replace('-', '_'), state_dict[key]) + self.__state = {} + for key, value in state_dict.iteritems(): + # don't store id in state dictionary -make it a proper instance attribute + if key == 'id': + self.id = value + self.__state[key.replace('-', '_')] = value def _get_child_tuples_by_type(self, desired_type): """Get a list of (name,dict) pairs from children of the specified type. @@ -114,21 +117,38 @@ class UnityIntrospectionObject(object): # user wants. for child_type, child_state in children: try: - if _object_registry[child_type] == desired_type: + if issubclass(_object_registry[child_type], desired_type): results.append((child_type, child_state)) except KeyError: pass return results - def get_children_by_type(self, desired_type): + def get_children_by_type(self, desired_type, **kwargs): """Get a list of children of the specified type. desired_type must be a subclass of UnityIntrospectionObject. + Keyword arguments can be used to restrict returned instances. For example: + + >>> get_children_by_type(Launcher, monitor=1) + + ... will return only LauncherInstances that have an attribute 'monitor' + that is equal to 1. + """ + self.refresh_state() result = [] for child in self._get_child_tuples_by_type(desired_type): - result.append(make_introspection_object(child)) + instance = make_introspection_object(child) + filters_passed = True + for attr, val in kwargs.iteritems(): + if not hasattr(instance, attr) or getattr(instance, attr) != val: + # Either attribute is not present, or is present but with + # the wrong value - don't add this instance to the results list. + filters_passed = False + break + if filters_passed: + result.append(instance) return result def refresh_state(self): @@ -138,8 +158,6 @@ class UnityIntrospectionObject(object): """ # need to get name from class object. - logger.debug("Refreshing state for %r", self) - new_state = get_state_by_name_and_id(self.__class__.__name__, self.id) self.set_properties(new_state) @@ -157,3 +175,15 @@ class UnityIntrospectionObject(object): cls_name = cls.__name__ instances = get_state_by_path("//%s" % (cls_name)) return [make_introspection_object((cls_name,i)) for i in instances] + + def __getattr__(self, name): + # avoid recursion if for some reason we have no state set (should never) + # happen. + if name == '__state': + raise AttributeError() + + if name in self.__state: + self.refresh_state() + return self.__state[name] + # attribute not found. + raise AttributeError("Attribute '%s' not found." % (name)) diff --git a/tests/autopilot/autopilot/emulators/unity/dash.py b/tests/autopilot/autopilot/emulators/unity/dash.py index 74401e30b..ace52fd48 100644 --- a/tests/autopilot/autopilot/emulators/unity/dash.py +++ b/tests/autopilot/autopilot/emulators/unity/dash.py @@ -7,26 +7,31 @@ # by the Free Software Foundation. # -from compizconfig import Setting, Plugin from time import sleep -from autopilot.globals import global_context -from autopilot.emulators.unity import get_state_by_path -from autopilot.emulators.X11 import Keyboard - +from autopilot.emulators.unity import ( + get_state_by_path, + make_introspection_object, + UnityIntrospectionObject, + ) +from autopilot.emulators.X11 import Keyboard, Mouse +from autopilot.keybindings import KeybindingsHelper import logging + + logger = logging.getLogger(__name__) -class Dash(object): +class Dash(KeybindingsHelper): """ An emulator class that makes it easier to interact with the unity dash. """ def __init__(self): - self.plugin = Plugin(global_context, "unityshell") - self.setting = Setting(self.plugin, "show_launcher") + self.controller = DashController.get_all_instances()[0] + self.view = self.controller.get_dash_view() + self._keyboard = Keyboard() super(Dash, self).__init__() @@ -35,15 +40,18 @@ class Dash(object): Reveals the dash if it's currently hidden, hides it otherwise. """ logger.debug("Toggling dash visibility with Super key.") - self._keyboard.press_and_release("Super") + self.keybinding("dash/reveal") sleep(1) - def ensure_visible(self): + def ensure_visible(self, clear_search=True): """ Ensures the dash is visible. """ if not self.get_is_visible(): self.toggle_reveal() + self._wait_for_visibility(expect_visible=True) + if clear_search: + self.clear_search() def ensure_hidden(self): """ @@ -51,78 +59,217 @@ class Dash(object): """ if self.get_is_visible(): self.toggle_reveal() + self._wait_for_visibility(expect_visible=False) + + def _wait_for_visibility(self, expect_visible): + for i in range(11): + if self.get_is_visible() != expect_visible: + sleep(1) + else: + return + raise RuntimeError("Dash not %s after waiting for 10 seconds." % + ("Visible" if expect_visible else "Hidden")) def get_is_visible(self): """ Is the dash visible? """ - return bool(get_state_by_path("/Unity/DashController")[0]["visible"]) - - def get_searchbar_geometry(self): - """Returns the searchbar geometry""" - search_bar = get_state_by_path("//SearchBar")[0] - return search_bar['x'], search_bar['y'], search_bar['width'], search_bar['height'] + return self.controller.visible - def get_search_string(self): - """ - Return the current dash search bar search string. - """ - return unicode(get_state_by_path("//SearchBar")[0]['search_string']) + def get_searchbar(self): + """Returns the searchbar attached to the dash.""" + return self.view.get_searchbar() - def searchbar_has_focus(self): - """ - Returns True if the search bar has the key focus, False otherwise. - """ - return get_state_by_path("//SearchBar")[0]['has_focus'] + def get_num_rows(self): + """Returns the number of displayed rows in the dash.""" + return self.view.num_rows - def get_current_lens(self): - """Returns the id of the current lens. + def clear_search(self): + """Clear the contents of the search bar. - For example, the default lens is 'home.lens', the run-command lens is - 'commands.lens'. + Assumes dash is already visible, and search bar has keyboard focus. """ - return unicode(get_state_by_path("//DashController/DashView/LensBar")[0]['active-lens']) - - def get_focused_lens_icon(self): - """Returns the id of the current focused icon.""" - return unicode(get_state_by_path("//DashController/DashView/LensBar")[0]['focused-lens-icon']) - - def get_num_rows(self): - """Returns the number of displayed rows in the dash.""" - return get_state_by_path("//DashController/DashView")[0]['num-rows'] + self._keyboard.press_and_release("Ctrl+a") + self._keyboard.press_and_release("Delete") - def reveal_application_lens(self): + def reveal_application_lens(self, clear_search=True): """Reveal the application lense.""" logger.debug("Revealing application lens with Super+a.") - self._keyboard.press('Super') - self._keyboard.press_and_release("a") - self._keyboard.release('Super') + self._reveal_lens("lens_reveal/apps") + if clear_search: + self.clear_search() - def reveal_music_lens(self): + def reveal_music_lens(self, clear_search=True): """Reveal the music lense.""" logger.debug("Revealing music lens with Super+m.") - self._keyboard.press('Super') - self._keyboard.press_and_release("m") - self._keyboard.release('Super') + self._reveal_lens("lens_reveal/music") - def reveal_file_lens(self): + def reveal_file_lens(self, clear_search=True): """Reveal the file lense.""" logger.debug("Revealing file lens with Super+f.") - self._keyboard.press('Super') - self._keyboard.press_and_release("f") - self._keyboard.release('Super') + self._reveal_lens("lens_reveal/files") + if clear_search: + self.clear_search() - def reveal_command_lens(self): + def reveal_command_lens(self, clear_search=True): """Reveal the 'run command' lens.""" logger.debug("Revealing command lens with Alt+F2.") - self._keyboard.press_and_release('Alt+F2') + self._reveal_lens("lens_reveal/command") + if clear_search: + self.clear_search() + + def _reveal_lens(self, binding_name): + self.keybinding_hold(binding_name) + self.keybinding_tap(binding_name) + self.keybinding_release(binding_name) + + def get_current_lens(self): + """Get the currently-active LensView object.""" + active_lens_name = self.view.get_lensbar().active_lens + return self.view.get_lensview_by_name(active_lens_name) + + +class DashController(UnityIntrospectionObject): + """The main dash controller object.""" + + def get_dash_view(self): + """Get the dash view that's attached to this controller.""" + return self.get_children_by_type(DashView)[0] + + +class DashView(UnityIntrospectionObject): + """The dash view.""" + + def get_searchbar(self): + """Get the search bar attached to this dash view.""" + return self.get_children_by_type(SearchBar)[0] + + def get_lensbar(self): + """Get the lensbar attached to this dash view.""" + return self.get_children_by_type(LensBar)[0] + + def get_lensview_by_name(self, lens_name): + """Get a LensView child object by it's name. For example, "home.lens".""" + lenses = self.get_children_by_type(LensView) + for lens in lenses: + if lens.name == lens_name: + return lens + + +class SearchBar(UnityIntrospectionObject): + """The search bar for the dash view.""" + + +class LensBar(UnityIntrospectionObject): + """The bar of lens icons at the bottom of the dash.""" + + +class LensView(UnityIntrospectionObject): + """A Lens View.""" def get_focused_category(self): - """Returns the current focused category. """ - groups = get_state_by_path("//PlacesGroup[header-has-keyfocus=True]") + """Return a PlacesGroup instance for the category whose header has keyboard focus. + + Returns None if no category headers have keyboard focus. + + """ + categories = self.get_children_by_type(PlacesGroup) + matches = [m for m in categories if m.header_has_keyfocus] + if matches: + return matches[0] + return None + + def get_category_by_name(self, category_name): + """Return a PlacesGroup instance with the given name, or None.""" + categories = self.get_children_by_type(PlacesGroup) + matches = [m for m in categories if m.name == category_name] + if matches: + return matches[0] + return None + + def get_num_visible_categories(self): + """Get the number of visible categories in this lens.""" + return len([c for c in self.get_children_by_type(PlacesGroup) if c.is_visible]) + + def get_filterbar(self): + """Get the filter bar for the current lense, or None if it doesn't have one.""" + bars = self.get_children_by_type(FilterBar) + if bars: + return bars[0] + return None + + +class PlacesGroup(UnityIntrospectionObject): + """A category in the lense view.""" + + def get_results(self): + """Get a list of all results within this category. May return an empty list.""" + result_view = self.get_children_by_type(ResultView)[0] + return result_view.get_children_by_type(Result) + + +class ResultView(UnityIntrospectionObject): + """Contains a list of Result objects.""" + + +class Result(UnityIntrospectionObject): + """A single result in the dash.""" + + +class FilterBar(UnityIntrospectionObject): + """A filterbar, as shown inside a lens.""" + + def get_num_filters(self): + """Get the number of filters in this filter bar.""" + filters = self.get_children_by_type(FilterExpanderLabel) + return len(filters) + + def get_focused_filter(self): + """Returns the id of the focused filter widget.""" + filters = self.get_children_by_type(FilterExpanderLabel) + for filter_label in filters: + if filter_label.expander_has_focus: + return filter_label + return None + + def is_expanded(self): + """Return True if the filterbar on this lens is expanded, False otherwise. + """ + searchbar = self._get_searchbar() + return searchbar.showing_filters + + def ensure_expanded(self): + """Expand the filter bar, if it's not already.""" + if not self.is_expanded(): + searchbar = self._get_searchbar() + tx = searchbar.filter_label_x + (searchbar.filter_label_width / 2) + ty = searchbar.filter_label_y + (searchbar.filter_label_height / 2) + m = Mouse() + m.move(tx, ty) + m.click() + + def ensure_collapsed(self): + """Collapse the filter bar, if it's not already.""" + if self.is_expanded(): + searchbar = self._get_searchbar() + tx = searchbar.filter_label_x + (searchbar.filter_label_width / 2) + ty = searchbar.filter_label_y + (searchbar.filter_label_height / 2) + m = Mouse() + m.move(tx, ty) + m.click() + + def _get_searchbar(self): + """Get the searchbar. + + This hack exists because there's now more than one SearchBar in Unity, + and for some reason the FilterBar stuff is bundled in the SearchBar. + + """ + state_info = get_state_by_path("//DashView/SearchBar") + assert(len(state_info) == 1) + return make_introspection_object(("SearchBar", state_info[0])) + - if len(groups) >= 1: - return groups[0] - else: - return None +class FilterExpanderLabel(UnityIntrospectionObject): + """A label that expands into a filter within a filter bar.""" diff --git a/tests/autopilot/autopilot/emulators/unity/hud.py b/tests/autopilot/autopilot/emulators/unity/hud.py index c79a23105..de0c7b95c 100644 --- a/tests/autopilot/autopilot/emulators/unity/hud.py +++ b/tests/autopilot/autopilot/emulators/unity/hud.py @@ -7,8 +7,50 @@ # by the Free Software Foundation. # +from autopilot.keybindings import KeybindingsHelper from autopilot.emulators.unity import UnityIntrospectionObject -class HudController(UnityIntrospectionObject): +class HudView(UnityIntrospectionObject): + """Proxy object for the hud view child of the controller.""" + + +class HudController(UnityIntrospectionObject, KeybindingsHelper): """Proxy object for the Unity Hud Controller.""" + + def ensure_hidden(self): + """Hides the hud if it's not already hidden.""" + if self.is_visible(): + self.toggle_reveal() + + def ensure_visible(self): + """Shows the hud if it's not already showing.""" + if not self.is_visible(): + self.toggle_reveal() + + def is_visible(self): + return self.visible + + def toggle_reveal(self, tap_delay=0.1): + """Tap the 'Alt' key to toggle the hud visibility.""" + self.keybinding("hud/reveal", tap_delay) + + def _get_view(self): + views = self.get_children_by_type(HudView) + return views[0] if views else None + + @property + def selected_button(self): + view = self._get_view() + if view: + return view.selected_button + else: + return 0 + + @property + def num_buttons(self): + view = self._get_view() + if view: + return view.num_buttons + else: + return 0 diff --git a/tests/autopilot/autopilot/emulators/unity/icons.py b/tests/autopilot/autopilot/emulators/unity/icons.py index 93b1d2c81..bd31489df 100644 --- a/tests/autopilot/autopilot/emulators/unity/icons.py +++ b/tests/autopilot/autopilot/emulators/unity/icons.py @@ -26,8 +26,6 @@ class SimpleLauncherIcon(UnityIntrospectionObject): launcher icon. """ - # quicklist is created dynamically, so refresh our state: - self.refresh_state() matches = self.get_children_by_type(Quicklist) return matches[0] if matches else None diff --git a/tests/autopilot/autopilot/emulators/unity/launcher.py b/tests/autopilot/autopilot/emulators/unity/launcher.py index ee35f3c39..7ead61957 100644 --- a/tests/autopilot/autopilot/emulators/unity/launcher.py +++ b/tests/autopilot/autopilot/emulators/unity/launcher.py @@ -10,35 +10,51 @@ import logging from time import sleep -from autopilot.emulators.unity import get_state_by_path, make_introspection_object +from autopilot.keybindings import KeybindingsHelper +from autopilot.emulators.unity import UnityIntrospectionObject from autopilot.emulators.unity.icons import BamfLauncherIcon, SimpleLauncherIcon -from autopilot.emulators.X11 import Keyboard, Mouse +from autopilot.emulators.X11 import Mouse, ScreenGeometry logger = logging.getLogger(__name__) -class Launcher(object): - """Interact with the unity Launcher.""" +class LauncherController(UnityIntrospectionObject): + """The LauncherController class.""" + + def get_launcher_for_monitor(self, monitor_num): + """Return an instance of Launcher for the specified monitor, or None.""" + launchers = self.get_children_by_type(Launcher, monitor=monitor_num) + return launchers[0] if launchers else None + + @property + def model(self): + """Return the launcher model.""" + models = LauncherModel.get_all_instances() + assert(len(models) == 1) + return models[0] + + def key_nav_monitor(self): + return self.key_nav_launcher_monitor + + +class Launcher(UnityIntrospectionObject, KeybindingsHelper): + """An individual launcher for a monitor.""" + + def __init__(self, *args, **kwargs): + super(Launcher, self).__init__(*args, **kwargs) - def __init__(self): - super(Launcher, self).__init__() - # set up base launcher vars - self.x = 0 - self.y = 120 - self.width = 0 - self.height = 0 self.show_timeout = 1 self.hide_timeout = 1 - self.grabbed = False - self._keyboard = Keyboard() - self._mouse = Mouse() + self.in_keynav_mode = False - state = self.__get_state(0) - self.icon_width = int(state['icon-size']) + self._mouse = Mouse() + self._screen = ScreenGeometry() - def move_mouse_to_right_of_launcher(self, monitor): - (x, y, w, h) = self.launcher_geometry(monitor) + def move_mouse_to_right_of_launcher(self): + """Places the mouse to the right of this launcher.""" + self._screen.move_mouse_to_monitor(self.monitor) + (x, y, w, h) = self.geometry target_x = x + w + 10 target_y = y + h / 2 @@ -46,156 +62,122 @@ class Launcher(object): self._mouse.move(target_x, target_y, False) sleep(self.show_timeout) - def move_mouse_over_launcher(self, monitor): - (x, y, w, h) = self.launcher_geometry(monitor) - self._screen.move_mouse_to_monitor(monitor) + def move_mouse_over_launcher(self): + """Move the mouse over this launcher.""" + self._screen.move_mouse_to_monitor(self.monitor) + (x, y, w, h) = self.geometry target_x = x + w / 2 target_y = y + h / 2 + logger.debug("Moving mouse to center of launcher.") self._mouse.move(target_x, target_y) - def reveal_launcher(self, monitor): - (x, y, w, h) = self.launcher_geometry(monitor) - logger.debug("Revealing launcher on monitor %d with mouse.", monitor) + def reveal_launcher(self): + """Reveal this launcher with the mouse.""" + self._screen.move_mouse_to_monitor(self.monitor) + (x, y, w, h) = self.geometry + + logger.debug("Revealing launcher on monitor %d with mouse.", self.monitor) self._mouse.move(x - 920, y + h / 2, True, 5, .002) sleep(self.show_timeout) def keyboard_reveal_launcher(self): + """Reveal this launcher using the keyboard.""" + self._screen.move_mouse_to_monitor(self.monitor) logger.debug("Revealing launcher with keyboard.") - self._keyboard.press('Super') + self.keybinding_hold("launcher/reveal") sleep(1) def keyboard_unreveal_launcher(self): + """Un-reveal this launcher using the keyboard.""" + self._screen.move_mouse_to_monitor(self.monitor) logger.debug("Un-revealing launcher with keyboard.") - self._keyboard.release('Super') + self.keybinding_release("launcher/reveal") sleep(1) def grab_switcher(self): + """Start keyboard navigation mode by pressing Alt+F1.""" + self._screen.move_mouse_to_monitor(self.monitor) logger.debug("Initiating launcher keyboard navigation with Alt+F1.") - self._keyboard.press_and_release('Alt+F1') - self.grabbed = True + self.keybinding("launcher/keynav") + self.in_keynav_mode = True def switcher_enter_quicklist(self): - if self.grabbed: - logger.debug("Opening quicklist for currently selected icon.") - self._keyboard.press_and_release('Right') + if not self.in_keynav_mode: + raise RuntimeError("Cannot open switcher quicklist while not in keynav mode.") + logger.debug("Opening quicklist for currently selected icon.") + self.keybinding("launcher/keynav/open-quicklist") def switcher_exit_quicklist(self): - if self.grabbed: - logger.debug("Closing quicklist for currently selected icon.") - self._keyboard.press_and_release('Left') + if not self.in_keynav_mode: + raise RuntimeError("Cannot close switcher quicklist while not in keynav mode.") + logger.debug("Closing quicklist for currently selected icon.") + self.keybinding("launcher/keynav/close-quicklist") def start_switcher(self): + """Start the super+Tab switcher on this launcher.""" + self._screen.move_mouse_to_monitor(self.monitor) logger.debug("Starting Super+Tab switcher.") - self._keyboard.press('Super+Tab') - self._keyboard.release('Tab') + self.keybinding_hold("launcher/switcher") + self.keybinding_tap("launcher/switcher") sleep(1) def end_switcher(self, cancel): + """End either the keynav mode or the super+tab swithcer. + + If cancel is True, the currently selected icon will not be activated. + + """ if cancel: logger.debug("Cancelling keyboard navigation mode.") - self._keyboard.press_and_release('Escape') - if self.grabbed != True: - self._keyboard.release('Super') + self.keybinding("launcher/keynav/exit") + if not self.in_keynav_mode: + self.keybinding_release("launcher/switcher") else: logger.debug("Ending keyboard navigation mode.") - if self.grabbed: - self._keyboard.press_and_release('\n') + if self.in_keynav_mode: + self.keybinding("launcher/keynav/activate") else: - self._keyboard.release('Super') - self.grabbed = False + self.keybinding_release("launcher/switcher") + self.in_keynav_mode = False def switcher_next(self): logger.debug("Selecting next item in keyboard navigation mode.") - if self.grabbed: - self._keyboard.press_and_release('Down') + if self.in_keynav_mode: + self.keybinding("launcher/keynav/next") else: - self._keyboard.press_and_release('Tab') + self.keybinding("launcher/switcher/next") def switcher_prev(self): logger.debug("Selecting previous item in keyboard navigation mode.") - if self.grabbed: - self._keyboard.press_and_release('Up') + if self.in_keynav_mode: + self.keybinding("launcher/keynav/prev") else: - self._keyboard.press_and_release('Shift+Tab') - - def is_quicklist_open(self, monitor): - state = self.__get_state(monitor) - return bool(state['quicklist-open']) + self.keybinding("launcher/switcher/prev") - def is_showing(self, monitor): - state = self.__get_state(monitor) - return not bool(state['hidden']) + def click_launcher_icon(self, icon, button=1): + """Move the mouse over the launcher icon, and click it. - def key_nav_is_active(self): - state = self.__get_controller_state() - return bool(state['key_nav_is_active']) + `icon` must be an instance of SimpleLauncherIcon or it's descendants. - def key_nav_monitor(self): - state = self.__get_controller_state() - return int(state['key_nav_launcher_monitor']) - - def key_nav_is_grabbed(self): - state = self.__get_controller_state() - return bool(state['key_nav_is_grabbed']) - - def key_nav_selection(self): - state = self.__get_controller_state() - return int(state['key_nav_selection']) - - def launcher_geometry(self, monitor): - state = self.__get_state(monitor) - x = int(state['x']) - y = int(state['y']) - width = int(state['width']) - height = int(state['height']) - return (x, y, width, height) - - def num_launchers(self): - return len(get_state_by_path('/Unity/LauncherController/Launcher')) - - def __get_controller_state(self): - return get_state_by_path('/Unity/LauncherController')[0] - - def __get_model_state(self): - return get_state_by_path('/Unity/LauncherController/LauncherModel')[0] - - def __get_state(self, monitor): - return get_state_by_path('/Unity/LauncherController/Launcher[monitor=%s]' % (monitor))[0] - - def get_launcher_icons(self, visible_only=True): - """Get a list of launcher icons in this launcher.""" - model = self.__get_model_state() - icons = [] - for child in model['Children']: - icon = make_introspection_object(child) - if icon: - if visible_only and not getattr(icon, 'quirk_visible', False): - continue - icons.append(icon) - return icons - - def num_launcher_icons(self): - """Get the number of icons in the launcher model.""" - return len(self.get_launcher_icons()) - - def get_currently_selected_icon(self): - """Returns the currently selected launcher icon, if keynav mode is active.""" - return self.get_launcher_icons()[self.key_nav_selection()] - - def click_launcher_icon(self, icon, monitor=0, button=1): - """Move the mouse over the launcher icon, and click it.""" + """ if not isinstance(icon, SimpleLauncherIcon): raise TypeError("icon must be a LauncherIcon") logger.debug("Clicking launcher icon %r on monitor %d with mouse button %d", - icon, monitor, button) - self.reveal_launcher(monitor) - self._mouse.move(icon.x, icon.y + (self.icon_width / 2)) + icon, self.monitor, button) + self.reveal_launcher() + target_x = icon.x + self.x + target_y = icon.y + (self.icon_size / 2) + self._mouse.move(target_x, target_y ) self._mouse.click(button) - self.move_mouse_to_right_of_launcher(monitor) + self.move_mouse_to_right_of_launcher() def lock_to_launcher(self, icon): - """lock 'icon' to the launcher, if it's not already.""" + """lock 'icon' to the launcher, if it's not already. + + `icon` must be an instance of BamfLauncherIcon. + + """ if not isinstance(icon, BamfLauncherIcon): raise TypeError("Can only lock instances of BamfLauncherIcon") if icon.sticky: @@ -205,13 +187,17 @@ class Launcher(object): logger.debug("Locking icon %r to launcher.", icon) self.click_launcher_icon(icon, button=3) quicklist = icon.get_quicklist() - pin_item = quicklist.get_quicklist_item_by_text('Lock to launcher') + pin_item = quicklist.get_quicklist_item_by_text('Lock to Launcher') quicklist.click_item(pin_item) def unlock_from_launcher(self, icon): - """lock 'icon' to the launcher, if it's not already.""" - if not isinstance(icon, SimpleLauncherIcon): - raise TypeError("icon must be a LauncherIcon") + """lock 'icon' to the launcher, if it's not already. + + `icon` must be an instance of BamfLauncherIcon. + + """ + if not isinstance(icon, BamfLauncherIcon): + raise TypeError("Can only unlock instances of BamfLauncherIcon") if not icon.sticky: # nothing to do. return @@ -219,5 +205,45 @@ class Launcher(object): logger.debug("Unlocking icon %r from launcher.") self.click_launcher_icon(icon, button=3) quicklist = icon.get_quicklist() - pin_item = quicklist.get_quicklist_item_by_text('Unlock from launcher') + pin_item = quicklist.get_quicklist_item_by_text('Unlock from Launcher') quicklist.click_item(pin_item) + + def is_quicklist_open(self): + return self.quicklist_open + + def is_showing(self): + return not self.hidden + + def are_shortcuts_showing(self): + return self.shortcuts_shown + + @property + def geometry(self): + """Returns a tuple of (x,y,w,h) for the current launcher.""" + return (self.x, self.y, self.width, self.height) + + +class LauncherModel(UnityIntrospectionObject): + """THe launcher model. Contains all launcher icons as children.""" + + def get_launcher_icons(self, visible_only=True): + """Get a list of launcher icons in this launcher.""" + if visible_only: + return self.get_children_by_type(SimpleLauncherIcon, quirk_visible=True) + else: + return self.get_children_by_type(SimpleLauncherIcon) + + def get_icon_by_tooltip_text(self, tooltip_text): + """Get a launcher icon given it's tooltip text. + + Returns None if there is no icon with the specified text. + """ + for icon in self.get_launcher_icons(): + if icon.tooltip_text == tooltip_text: + return icon + return None + + def num_launcher_icons(self): + """Get the number of icons in the launcher model.""" + return len(self.get_launcher_icons()) + diff --git a/tests/autopilot/autopilot/emulators/unity/quicklist.py b/tests/autopilot/autopilot/emulators/unity/quicklist.py index c5bfc0c3f..40a8623be 100644 --- a/tests/autopilot/autopilot/emulators/unity/quicklist.py +++ b/tests/autopilot/autopilot/emulators/unity/quicklist.py @@ -23,15 +23,12 @@ class Quicklist(UnityIntrospectionObject): @property def items(self): """Individual items in the quicklist.""" - return [self.__make_quicklist_from_data(ctype, cdata) for ctype, cdata in self._children] + return self.get_children_by_type(QuicklistMenuItem) def get_quicklist_item_by_text(self, text): """Returns a QuicklistMenuItemLabel object with the given text, or None.""" if not self.active: - # try refreshing the state, otherwise bail: - self.refresh_state() - if not self.active: - raise RuntimeError("Cannot get quicklist items. Quicklist is inactive!") + raise RuntimeError("Cannot get quicklist items. Quicklist is inactive!") matches = filter(lambda i: i.text == text, self.get_children_by_type(QuicklistMenuItemLabel)) diff --git a/tests/autopilot/autopilot/emulators/unity/switcher.py b/tests/autopilot/autopilot/emulators/unity/switcher.py index a91eaf49e..2787aa2f4 100644 --- a/tests/autopilot/autopilot/emulators/unity/switcher.py +++ b/tests/autopilot/autopilot/emulators/unity/switcher.py @@ -10,14 +10,20 @@ import logging from time import sleep +from autopilot.keybindings import KeybindingsHelper from autopilot.emulators.unity import get_state_by_path, make_introspection_object from autopilot.emulators.X11 import Keyboard +# even though we don't use these directly, we need to make sure they've been +# imported so the classes contained are registered with the introspection API. +from autopilot.emulators.unity.icons import * logger = logging.getLogger(__name__) -class Switcher(object): +# TODO: THis class needs to be ported to the new-style emulator classes. +# See launcher.py or dash.py for reference. +class Switcher(KeybindingsHelper): """Interact with the Unity switcher.""" def __init__(self): @@ -27,56 +33,79 @@ class Switcher(object): def initiate(self): """Start the switcher with alt+tab.""" logger.debug("Initiating switcher with Alt+Tab") - self._keyboard.press('Alt') - self._keyboard.press_and_release('Tab') + self.keybinding_hold("switcher/reveal_normal") + self.keybinding_tap("switcher/reveal_normal") sleep(1) def initiate_detail_mode(self): """Start detail mode with alt+`""" logger.debug("Initiating switcher detail mode with Alt+`") - self._keyboard.press('Alt') - self._keyboard.press_and_release('`') + self.keybinding_hold("switcher/reveal_details") + self.keybinding_tap("switcher/reveal_details") + sleep(1) + + def initiate_all_mode(self): + """Start switcher in 'all workspaces' mode. + + Shows apps from all workspaces, instead of just the current workspace. + """ + logger.debug("Initiating switcher in 'all workspaces' mode.") + self.keybinding_hold("switcher/reveal_all") + self.keybinding_tap("switcher/reveal_all") + sleep(1) + + def initiate_right_arrow(self): + """Impropperly attempt to start switcher.""" + logger.debug("Initiating switcher with Alt+Right (should not work)") + self.keybinding_hold("switcher/reveal_impropper") + self.keybinding_tap("switcher/right") + sleep(1) def terminate(self): """Stop switcher without activating the selected icon.""" logger.debug("Terminating switcher.") - self._keyboard.press_and_release('Escape') - self._keyboard.release('Alt') + self.keybinding("switcher/cancel") + self.keybinding_release("switcher/reveal_normal") + + def cancel(self): + """Stop switcher without activating the selected icon and releasing the keys.""" + logger.debug("Cancelling switcher.") + self.keybinding("switcher/cancel") def stop(self): """Stop switcher and activate the selected icon.""" logger.debug("Stopping switcher") - self._keyboard.release('Alt') + self.keybinding_release("switcher/reveal_normal") def next_icon(self): """Move to the next application.""" logger.debug("Selecting next item in switcher.") - self._keyboard.press_and_release('Tab') + self.keybinding("switcher/next") def previous_icon(self): """Move to the previous application.""" logger.debug("Selecting previous item in switcher.") - self._keyboard.press_and_release('Shift+Tab') + self.keybinding("switcher/prev") def show_details(self): """Show detail mode.""" logger.debug("Showing details view.") - self._keyboard.press_and_release('`') + self.keybinding("switcher/detail_start") def hide_details(self): """Hide detail mode.""" logger.debug("Hiding details view.") - self._keyboard.press_and_release('Up') + self.keybinding("switcher/detail_stop") def next_detail(self): """Move to next detail in the switcher.""" logger.debug("Selecting next item in details mode.") - self._keyboard.press_and_release('`') + self.keybinding("switcher/detail_next") def previous_detail(self): """Move to the previous detail in the switcher.""" logger.debug("Selecting previous item in details mode.") - self._keyboard.press_and_release('Shift+`') + self.keybinding("switcher/detail_prev") def __get_icon(self, index): return self.__get_model()['Children'][index][1][0] @@ -114,6 +143,10 @@ class Switcher(object): def get_is_visible(self): return bool(self.__get_controller()['visible']) + def get_is_in_details_mode(self): + """Return True if the SwitcherView is in details mode.""" + return bool(self.__get_model()['detail-selection']) + def get_switcher_icons(self): """Get all icons in the switcher model. @@ -132,4 +165,4 @@ class Switcher(object): return get_state_by_path('/Unity/SwitcherController/SwitcherModel')[0] def __get_controller(self): - return get_state_by_path('/unity/SwitcherController')[0] + return get_state_by_path('/Unity/SwitcherController')[0] diff --git a/tests/autopilot/autopilot/emulators/unity/workspace.py b/tests/autopilot/autopilot/emulators/unity/workspace.py new file mode 100644 index 000000000..6fdb16907 --- /dev/null +++ b/tests/autopilot/autopilot/emulators/unity/workspace.py @@ -0,0 +1,94 @@ +# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*- +# Copyright 2012 Canonical +# Author: Thomi Richards +# +# This program is free software: you can redistribute it and/or modify it +# under the terms of the GNU General Public License version 3, as published +# by the Free Software Foundation. +# + +from compizconfig import Plugin, Setting + +from autopilot.globals import global_context +from autopilot.keybindings import KeybindingsHelper +from autopilot.utilities import get_desktop_viewport, get_desktop_geometry + + +class WorkspaceManager(KeybindingsHelper): + """Class to manage switching to different workspaces.""" + + def __init__(self): + super(WorkspaceManager, self).__init__() + self.refresh_workspace_information() + + @property + def num_workspaces(self): + """The number of workspaces configured.""" + return self._workspaces_wide * self._workspaces_high + + @property + def current_workspace(self): + """The current workspace number. 0 <= x < num_workspaces.""" + vx,vy = get_desktop_viewport() + return self._coordinates_to_vp_number(vx, vy) + + def refresh_workspace_information(self): + """Re-read information about available workspaces from compiz and X11.""" + self._workspaces_wide = self._get_compiz_option("core", "hsize") + self._workspaces_high = self._get_compiz_option("core", "vsize") + self._desktop_width, self.desktop_height = get_desktop_geometry() + self._viewport_width = self._desktop_width / self._workspaces_wide + self._viewport_height = self.desktop_height / self._workspaces_high + + def switch_to(self, workspace_num): + """Switch to the workspace specified. + + ValueError is raised if workspace_num is outside 0<= workspace_num < num_workspaces. + + """ + if workspace_num < 0 or workspace_num >= self.num_workspaces: + raise ValueError("Workspace number must be between 0 and %d" % self.num_workspaces) + + current_row, current_col = self._vp_number_to_row_col(self.current_workspace) + target_row, target_col = self._vp_number_to_row_col(workspace_num) + lefts = rights = ups = downs = 0 + if current_col > target_col: + lefts = current_col - target_col + else: + rights = target_col - current_col + if current_row > target_row: + ups = current_row - target_row + else: + downs = target_row - current_row + + for i in range(lefts): + self.keybinding("workspace/move_left") + for i in range(rights): + self.keybinding("workspace/move_right") + for i in range(ups): + self.keybinding("workspace/move_up") + for i in range(downs): + self.keybinding("workspace/move_down") + + + def _coordinates_to_vp_number(self, vx, vy): + """Translate viewport coordinates to a viewport number.""" + row,col = self._coordinates_to_row_col(vx, vy) + return (row * self._workspaces_wide) + col + + def _coordinates_to_row_col(self, vx, vy): + """Translate viewport coordinates to viewport row,col.""" + row = vy / self._viewport_height + col = vx / self._viewport_width + return (row,col) + + def _vp_number_to_row_col(self, vp_number): + """Translate a viewport number to a viewport row/col.""" + row = vp_number / self._workspaces_wide + col = vp_number % self._workspaces_wide + return (row,col) + + def _get_compiz_option(self, plugin_name, setting_name): + plugin = Plugin(global_context, plugin_name) + setting = Setting(plugin, setting_name) + return setting.Value diff --git a/tests/autopilot/autopilot/globals.py b/tests/autopilot/autopilot/globals.py index ecdcec3fb..647739a02 100644 --- a/tests/autopilot/autopilot/globals.py +++ b/tests/autopilot/autopilot/globals.py @@ -1,3 +1,9 @@ from compizconfig import Context global_context = Context() + +# this can be set to True, in which case tests will be recorded. +video_recording_enabled = False + +# this is where videos will be put after being encoded. +video_record_directory = "/tmp/autopilot" diff --git a/tests/autopilot/autopilot/keybindings.py b/tests/autopilot/autopilot/keybindings.py new file mode 100644 index 000000000..d363be618 --- /dev/null +++ b/tests/autopilot/autopilot/keybindings.py @@ -0,0 +1,226 @@ +# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*- +# Copyright 2012 Canonical +# Author: Thomi Richards +# +# This program is free software: you can redistribute it and/or modify it +# under the terms of the GNU General Public License version 3, as published +# by the Free Software Foundation. + +"""Utility functions to get shortcut keybindings for various parts of Unity. + +Inside autopilot we deal with keybindings by naming them with unique names. For +example, instead of hard-coding the fact that 'Alt+F2' opens the command lens, we +might call: + +>>> keybindings.get('lens_reveal/command') +'Alt+F2' + +Keybindings come from two different places: + 1) Keybindings from compiz. We can get these if we have the plugin name and + setting name. + 2) Elsewhere. Right now we're hard-coding these in a separate dictionary. +""" + +from compizconfig import Plugin, Setting +import logging +from types import NoneType +import re + +from autopilot.emulators.X11 import Keyboard +from autopilot.globals import global_context + + +logger = logging.getLogger(__name__) + + +# +# Fill this dictionary with keybindings we want to store. +# +# If keybindings are from compizconfig, the value should be a 2-value tuple +# containging (plugin_name, setting_name). +# +# If keybindings are elsewhere, just store the keybinding string. +_keys = { + # Launcher: + "launcher/reveal": ('unityshell', 'show_launcher'), + "launcher/keynav": ('unityshell', 'keyboard_focus'), + "launcher/keynav/next": "Down", + "launcher/keynav/prev": "Up", + "launcher/keynav/activate": "Enter", + "launcher/keynav/exit": "Escape", + "launcher/switcher": ('unityshell', 'launcher_switcher_forward'), + "launcher/switcher/next": "Tab", + "launcher/switcher/prev": "Shift+Tab", + "launcher/keynav/open-quicklist": "Right", + "launcher/keynav/close-quicklist": "Left", + # Dash: + "dash/reveal": "Super", + # Lenses + "lens_reveal/command": ("unityshell", "execute_command"), + "lens_reveal/apps": "Super+a", + "lens_reveal/files": "Super+f", + "lens_reveal/music": "Super+m", + # Hud + "hud/reveal": ("unityshell", "show_hud"), + # Switcher: + "switcher/reveal_normal": ("unityshell", "alt_tab_forward"), + "switcher/reveal_impropper": "Alt+Right", + "switcher/reveal_details": "Alt+`", + "switcher/reveal_all": ("unityshell", "alt_tab_forward_all"), + "switcher/cancel": "Escape", + # These are in compiz as 'Alt+Right' and 'Alt+Left', but the fact that it + # lists the Alt key won't work for us, so I'm defining them manually. + "switcher/next": "Tab", + "switcher/prev": "Shift+Tab", + "switcher/right": "Right", + "switcher/left": "Left", + "switcher/detail_start": "Down", + "switcher/detail_stop": "Up", + "switcher/detail_next": "`", + "switcher/detail_prev": "`", + # Workspace switcher (wall): + "workspace/move_left": ("wall", "left_key"), + "workspace/move_right": ("wall", "right_key"), + "workspace/move_up": ("wall", "up_key"), + "workspace/move_down": ("wall", "down_key"), + # Window management + "window/minimize": ("core", "minimize_window_key"), +} + + + +def get(binding_name): + """Get a keyubinding, given it's well-known name. + + binding_name must be a string, or a TypeError will be raised. + + If binding_name cannot be found in the bindings dictionaries, a ValueError + will be raised. + + """ + if not isinstance(binding_name, basestring): + raise TypeError("binding_name must be a string.") + if binding_name not in _keys: + raise ValueError("Unknown binding name '%s'." % (binding_name)) + v = _keys[binding_name] + if isinstance(v, basestring): + return v + else: + return _get_compiz_keybinding(v) + + +def get_hold_part(binding_name): + """Returns the part of a keybinding that must be held permenantly. + + Use this function to split bindings like "Alt+Tab" into the part that must be + held down. See get_tap_part for the part that must be tapped. + + Raises a ValueError if the binding specified does not have multiple parts. + + """ + binding = get(binding_name) + parts = binding.split('+') + if len(parts) == 1: + logger.warning("Key binding '%s' does not have a hold part.", binding_name) + return parts[0] + return '+'.join(parts[:-1]) + + +def get_tap_part(binding_name): + """Returns the part of a keybinding that must be tapped. + + Use this function to split bindings like "Alt+Tab" into the part that must be + held tapped. See get_hold_part for the part that must be held down. + + Raises a ValueError if the binding specified does not have multiple parts. + + """ + binding = get(binding_name) + parts = binding.split('+') + if len(parts) == 1: + logger.warning("Key binding '%s' does not have a tap part.", binding_name) + return parts[0] + return parts[-1] + + +def _get_compiz_keybinding(compiz_tuple): + """Given a keybinding name, get the keybinding string from the compiz option. + + Raises ValueError if the compiz setting described does not hold a keybinding. + Raises RuntimeError if the compiz keybinding has been disabled. + + """ + plugin_name, setting_name = compiz_tuple + plugin = Plugin(global_context, plugin_name) + setting = Setting(plugin, setting_name) + if setting.Type != 'Key': + raise ValueError("Key binding maps to a compiz option that does not hold a keybinding.") + if not plugin.Enabled: + logger.warning("Returning keybinding for '%s' which is in un-enabled plugin '%s'", + setting.ShortDesc, + plugin.ShortDesc) + if setting.Value == "Disabled": + raise RuntimeError("Keybinding '%s' in compiz plugin '%s' has been disabled." % + setting.ShortDesc, plugin.ShortDesc) + + return _translate_compiz_keystroke_string(setting.Value) + + +def _translate_compiz_keystroke_string(keystroke_string): + """Get a string representing the keystroke stored in `keystroke_string`. + + `keystroke_string` is a compizconfig-style keystroke string. + + The returned value is suitable for passing into the Keyboard emulator. + + """ + if not isinstance(keystroke_string, basestring): + raise TypeError("keystroke string must be a string.") + + translations = { + 'Control': 'Ctrl', + 'Primary': 'Ctrl', + } + regex = re.compile('[<>]') + parts = regex.split(keystroke_string) + result = [] + for part in parts: + part = part.strip() + if part != "" and not part.isspace(): + translated = translations.get(part, part) + if translated not in result: + result.append(translated) + + return '+'.join(result) + + +class KeybindingsHelper(object): + """A helper class that makes it easier to use unity keybindings.""" + _keyboard = Keyboard() + + def keybinding(self, binding_name, delay=None): + """Press and release the keybinding with the given name. + + If set, the delay parameter will override the default delay set by the + keyboard emulator. + + """ + if type(delay) not in (float, NoneType): + raise TypeError("delay parameter must be a float if it is defined.") + if delay: + self._keyboard.press_and_release(get(binding_name), delay) + else: + self._keyboard.press_and_release(get(binding_name)) + + def keybinding_hold(self, binding_name): + """Hold down the hold-part of a keybinding.""" + self._keyboard.press(get_hold_part(binding_name)) + + def keybinding_release(self, binding_name): + """Release the hold-part of a keybinding.""" + self._keyboard.release(get_hold_part(binding_name)) + + def keybinding_tap(self, binding_name): + """Tap the tap-part of a keybinding.""" + self._keyboard.press_and_release(get_tap_part(binding_name)) + diff --git a/tests/autopilot/autopilot/tests/__init__.py b/tests/autopilot/autopilot/tests/__init__.py index 12982ab90..399c23d07 100644 --- a/tests/autopilot/autopilot/tests/__init__.py +++ b/tests/autopilot/autopilot/tests/__init__.py @@ -2,17 +2,36 @@ Autopilot tests for Unity. """ + +from compizconfig import Setting, Plugin +import logging +import os +from StringIO import StringIO +from subprocess import call, Popen, PIPE, STDOUT +from testscenarios import TestWithScenarios from testtools import TestCase from testtools.content import text_content +from testtools.matchers import Equals import time -import logging -from StringIO import StringIO +from autopilot.emulators.bamf import Bamf +from autopilot.emulators.unity.launcher import LauncherController +from autopilot.emulators.unity.switcher import Switcher +from autopilot.emulators.unity.workspace import WorkspaceManager from autopilot.emulators.X11 import Keyboard, Mouse +from autopilot.glibrunner import GlibRunner +from autopilot.globals import (global_context, + video_recording_enabled, + video_record_directory, + ) +from autopilot.keybindings import KeybindingsHelper -class AutopilotTestCase(TestCase): - """Wrapper around testtools.TestCase that takes care of some cleaning.""" +logger = logging.getLogger(__name__) + + +class LoggedTestCase(TestWithScenarios, TestCase): + """Initialize the logging for the test case.""" def setUp(self): @@ -34,19 +53,178 @@ class AutopilotTestCase(TestCase): log_format = "%(asctime)s %(levelname)s %(pathname)s:%(lineno)d - %(message)s" handler.setFormatter(MyFormatter(log_format)) root_logger.addHandler(handler) + #Tear down logging in a cleanUp handler, so it's done after all other + # tearDown() calls and cleanup handlers. + self.addCleanup(self.tearDownLogging) + # The reason that the super setup is done here is due to making sure + # that the logging is properly set up prior to calling it. + super(LoggedTestCase, self).setUp() - super(AutopilotTestCase, self).setUp() - - def tearDown(self): - Keyboard.cleanup() - Mouse.cleanup() - + def tearDownLogging(self): logger = logging.getLogger() for handler in logger.handlers: handler.flush() self._log_buffer.seek(0) self.addDetail('test-log', text_content(self._log_buffer.getvalue())) logger.removeHandler(handler) - #self._log_buffer.close() + # Calling del to remove the handler and flush the buffer. We are + # abusing the log handlers here a little. del self._log_buffer - super(AutopilotTestCase, self).tearDown() + + +class VideoCapturedTestCase(LoggedTestCase): + """Video capture autopilot tests, saving the results if the test failed.""" + + _recording_app = '/usr/bin/recordmydesktop' + _recording_opts = ['--no-sound', '--no-frame', '-o',] + + def setUp(self): + super(VideoCapturedTestCase, self).setUp() + global video_recording_enabled + if video_recording_enabled and not self._have_recording_app(): + video_recording_enabled = False + logger.warning("Disabling video capture since '%s' is not present", self._recording_app) + + if video_recording_enabled: + self._test_passed = True + self.addOnException(self._on_test_failed) + self.addCleanup(self._stop_video_capture) + self._start_video_capture() + + def _have_recording_app(self): + return os.path.exists(self._recording_app) + + def _start_video_capture(self): + args = self._get_capture_command_line() + self._capture_file = self._get_capture_output_file() + self._ensure_directory_exists_but_not_file(self._capture_file) + args.append(self._capture_file) + logger.debug("Starting: %r", args) + self._capture_process = Popen(args, stdout=PIPE, stderr=STDOUT) + + def _stop_video_capture(self): + """Stop the video capture. If the test failed, save the resulting file.""" + + if self._test_passed: + # We use kill here because we don't want the recording app to start + # encoding the video file (since we're removing it anyway.) + self._capture_process.kill() + self._capture_process.wait() + else: + self._capture_process.terminate() + self._capture_process.wait() + if self._capture_process.returncode != 0: + self.addDetail('video capture log', text_content(self._capture_process.stdout.read())) + self._capture_process = None + + def _get_capture_command_line(self): + return [self._recording_app] + self._recording_opts + + def _get_capture_output_file(self): + return os.path.join(video_record_directory, '%s.ogv' % (self.shortDescription())) + + def _ensure_directory_exists_but_not_file(self, file_path): + dirpath = os.path.dirname(file_path) + if not os.path.exists(dirpath): + os.makedirs(dirpath) + elif os.path.exists(file_path): + logger.warning("Video capture file '%s' already exists, deleting.", file_path) + os.remove(file_path) + + def _on_test_failed(self, ex_info): + """Called when a test fails.""" + self._test_passed = False + + +class AutopilotTestCase(VideoCapturedTestCase, KeybindingsHelper): + """Wrapper around testtools.TestCase that takes care of some cleaning.""" + + run_test_with = GlibRunner + + KNOWN_APPS = { + 'Character Map' : { + 'desktop-file': 'gucharmap.desktop', + 'process-name': 'gucharmap', + }, + 'Calculator' : { + 'desktop-file': 'gcalctool.desktop', + 'process-name': 'gcalctool', + }, + 'Mahjongg' : { + 'desktop-file': 'mahjongg.desktop', + 'process-name': 'mahjongg', + }, + 'Remmina' : { + 'desktop-file': 'remmina.desktop', + 'process-name': 'remmina', + }, + 'Text Editor' : { + 'desktop-file': 'gedit.desktop', + 'process-name': 'gedit', + }, + } + + def setUp(self): + super(AutopilotTestCase, self).setUp() + self.bamf = Bamf() + self.keyboard = Keyboard() + self.mouse = Mouse() + self.switcher = Switcher() + self.workspace = WorkspaceManager() + self.launcher = self._get_launcher_controller() + self.addCleanup(self.workspace.switch_to, self.workspace.current_workspace) + self.addCleanup(Keyboard.cleanup) + self.addCleanup(Mouse.cleanup) + + def start_app(self, app_name): + """Start one of the known apps, and kill it on tear down.""" + logger.info("Starting application '%s'", app_name) + app = self.KNOWN_APPS[app_name] + self.bamf.launch_application(app['desktop-file']) + self.addCleanup(call, ["killall", app['process-name']]) + + def close_all_app(self, app_name): + """Close all instances of the app_name.""" + app = self.KNOWN_APPS[app_name] + call(["killall", app['process-name']]) + super(LoggedTestCase, self).tearDown() + + def get_app_instances(self, app_name): + """Get BamfApplication instances for app_name.""" + desktop_file = self.KNOWN_APPS[app_name]['desktop-file'] + return self.bamf.get_running_applications_by_desktop_file(desktop_file) + + def app_is_running(self, app_name): + """Returns true if an instance of the application is running.""" + apps = self.get_app_instances(app_name) + return len(apps) > 0 + + def set_unity_option(self, option_name, option_value): + """Set an option in the unity compiz plugin options. + + The value will be set for the current test only. + + """ + self.set_compiz_option("unityshell", option_name, option_value) + + def set_compiz_option(self, plugin_name, setting_name, setting_value): + """Set setting `setting_name` in compiz plugin `plugin_name` to value `setting_value` + for one test only. + """ + old_value = self._set_compiz_option(plugin_name, setting_name, setting_value) + self.addCleanup(self._set_compiz_option, plugin_name, setting_name, old_value) + + def _set_compiz_option(self, plugin_name, option_name, option_value): + logger.info("Setting compiz option '%s' in plugin '%s' to %r", + option_name, plugin_name, option_value) + plugin = Plugin(global_context, plugin_name) + setting = Setting(plugin, option_name) + old_value = setting.Value + setting.Value = option_value + global_context.Write() + return old_value + + def _get_launcher_controller(self): + controllers = LauncherController.get_all_instances() + self.assertThat(len(controllers), Equals(1)) + return controllers[0] diff --git a/tests/autopilot/autopilot/tests/test_command_lens.py b/tests/autopilot/autopilot/tests/test_command_lens.py new file mode 100644 index 000000000..889eaeb5b --- /dev/null +++ b/tests/autopilot/autopilot/tests/test_command_lens.py @@ -0,0 +1,79 @@ +# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*- +# Copyright 2012 Canonical +# Author: Thomi Richards +# +# This program is free software: you can redistribute it and/or modify it +# under the terms of the GNU General Public License version 3, as published +# by the Free Software Foundation. + +from subprocess import call +from time import sleep + +from autopilot.emulators.bamf import Bamf +from autopilot.emulators.unity.dash import Dash +from autopilot.emulators.X11 import Keyboard +from autopilot.tests import AutopilotTestCase + + +class CommandLensSearchTests(AutopilotTestCase): + """Test the command lense search bahavior.""" + + def setUp(self): + self.dash = Dash() + super(CommandLensSearchTests, self).setUp() + + def tearDown(self): + self.dash.ensure_hidden() + super(CommandLensSearchTests, self).tearDown() + + def test_no_results(self): + """An empty string should get no results.""" + kb = Keyboard() + self.dash.reveal_command_lens() + command_lens = self.dash.get_current_lens() + + if self.dash.get_searchbar().search_string != "": + kb.press_and_release("Delete") + + sleep(1) + results_category = command_lens.get_category_by_name("Results") + self.assertFalse(results_category.is_visible) + + def test_results_category_appears(self): + """Results category must appear when there are some results.""" + kb = Keyboard() + self.dash.reveal_command_lens() + command_lens = self.dash.get_current_lens() + # lots of apps start with 'a'... + kb.type("a") + sleep(1) + results_category = command_lens.get_category_by_name("Results") + self.assertTrue(results_category.is_visible) + + def test_result_category_actually_contains_results(self): + """With a search string of 'a', the results category must contain some results.""" + kb = Keyboard() + self.dash.reveal_command_lens() + command_lens = self.dash.get_current_lens() + # lots of apps start with 'a'... + kb.type("a") + sleep(1) + results_category = command_lens.get_category_by_name("Results") + results = results_category.get_results() + self.assertTrue(results) + + def test_run_before_refresh(self): + """Hitting enter before view has updated results must run the correct command.""" + if self.app_is_running("Text Editor"): + self.close_all_app("Text Editor") + sleep(1) + + kb = Keyboard() + self.dash.reveal_command_lens() + kb.type("g") + sleep(1) + kb.type("edit", 0.1) + kb.press_and_release("Enter", 0.1) + self.addCleanup(self.close_all_app, "Text Editor") + app_found = self.bamf.wait_until_application_is_running("gedit.desktop", 5) + self.assertTrue(app_found) diff --git a/tests/autopilot/autopilot/tests/test_dash.py b/tests/autopilot/autopilot/tests/test_dash.py index 2e4566c05..b7ad182cb 100644 --- a/tests/autopilot/autopilot/tests/test_dash.py +++ b/tests/autopilot/autopilot/tests/test_dash.py @@ -6,24 +6,23 @@ # under the terms of the GNU General Public License version 3, as published # by the Free Software Foundation. +from gtk import Clipboard from time import sleep from autopilot.emulators.unity.dash import Dash from autopilot.emulators.X11 import Keyboard, Mouse from autopilot.tests import AutopilotTestCase -from autopilot.glibrunner import GlibRunner -class DashTests(AutopilotTestCase): - """Test the unity Dash.""" - run_test_with = GlibRunner +class DashRevealTests(AutopilotTestCase): + """Test the unity Dash Reveal.""" def setUp(self): - super(DashTests, self).setUp() + super(DashRevealTests, self).setUp() self.dash = Dash() def tearDown(self): - super(DashTests, self).tearDown() + super(DashRevealTests, self).tearDown() self.dash.ensure_hidden() def test_dash_reveal(self): @@ -38,148 +37,355 @@ class DashTests(AutopilotTestCase): def test_dash_keyboard_focus(self): """Dash must put keyboard focus on the search bar at all times.""" - self.dash.ensure_hidden() - - self.assertFalse(self.dash.get_is_visible()) + self.dash.ensure_visible() kb = Keyboard() - self.dash.toggle_reveal() kb.type("Hello") sleep(1) - self.assertEqual(self.dash.get_search_string(), u'Hello') - self.dash.toggle_reveal() - self.assertFalse(self.dash.get_is_visible()) + searchbar = self.dash.get_searchbar() + self.assertEqual(searchbar.search_string, u'Hello') def test_application_lens_shortcut(self): """Application lense must reveal when Super+a is pressed.""" self.dash.ensure_hidden() self.dash.reveal_application_lens() - self.assertEqual(self.dash.get_current_lens(), u'applications.lens') + lensbar = self.dash.view.get_lensbar() + self.assertEqual(lensbar.active_lens, u'applications.lens') def test_music_lens_shortcut(self): """Music lense must reveal when Super+w is pressed.""" self.dash.ensure_hidden() self.dash.reveal_music_lens() - self.assertEqual(self.dash.get_current_lens(), u'music.lens') + lensbar = self.dash.view.get_lensbar() + self.assertEqual(lensbar.active_lens, u'music.lens') def test_file_lens_shortcut(self): """File lense must reveal when Super+f is pressed.""" self.dash.ensure_hidden() self.dash.reveal_file_lens() - self.assertEqual(self.dash.get_current_lens(), u'files.lens') + lensbar = self.dash.view.get_lensbar() + self.assertEqual(lensbar.active_lens, u'files.lens') def test_command_lens_shortcut(self): """Run Command lens must reveat on alt+F2.""" self.dash.ensure_hidden() self.dash.reveal_command_lens() - self.assertEqual(self.dash.get_current_lens(), u'commands.lens') + lensbar = self.dash.view.get_lensbar() + self.assertEqual(lensbar.active_lens, u'commands.lens') + - def test_lensbar_keyfocus(self): - """Test that the lensbar keynavigation works well.""" +class DashKeyNavTests(AutopilotTestCase): + """Test the unity Dash keyboard navigation.""" + + def setUp(self): + super(DashKeyNavTests, self).setUp() + self.dash = Dash() + + def tearDown(self): + super(DashKeyNavTests, self).tearDown() self.dash.ensure_hidden() - self.dash.toggle_reveal() - kb = Keyboard() + def test_lensbar_gets_keyfocus(self): + """Test that the lensbar gets key focus after using Down keypresses.""" + self.dash.ensure_visible() + kb = Keyboard() # Make sure that the lens bar can get the focus for i in range(self.dash.get_num_rows()): - kb.press_and_release("Down") - self.assertIsNot(self.dash.get_focused_lens_icon(), '') + kb.press_and_release("Down") + lensbar = self.dash.view.get_lensbar() + self.assertIsNot(lensbar.focused_lens_icon, '') + + def test_lensbar_focus_changes(self): + """Lensbar focused icon should change with Left and Right keypresses.""" + self.dash.ensure_visible() + kb = Keyboard() + #put KB focus on lensbar again: + for i in range(self.dash.get_num_rows()): + kb.press_and_release("Down") - # Make sure that left - right work well - temp = self.dash.get_focused_lens_icon() + lensbar = self.dash.view.get_lensbar() + current_focused_icon = lensbar.focused_lens_icon kb.press_and_release("Right"); - self.assertIsNot(self.dash.get_focused_lens_icon(), temp) + lensbar.refresh_state() + self.assertNotEqual(lensbar.focused_lens_icon, current_focused_icon) kb.press_and_release("Left") - self.assertEqual(self.dash.get_focused_lens_icon(), temp) + lensbar.refresh_state() + self.assertEqual(lensbar.focused_lens_icon, current_focused_icon) - # Make sure that pressing 'Enter' we can change the lens... + def test_lensbar_enter_activation(self): + """Must be able to activate LensBar icons that have focus with an Enter keypress.""" + self.dash.ensure_visible() + kb = Keyboard() + #put KB focus on lensbar again: + for i in range(self.dash.get_num_rows()): + kb.press_and_release("Down") kb.press_and_release("Right"); - temp = self.dash.get_focused_lens_icon(); + lensbar = self.dash.view.get_lensbar() + focused_icon = lensbar.focused_lens_icon kb.press_and_release("Enter"); - self.assertEqual(self.dash.get_current_lens(), temp) - - # ... the lens bar should lose the key focus - self.assertEqual(self.dash.get_focused_lens_icon(), "") + lensbar.refresh_state() + self.assertEqual(lensbar.active_lens, focused_icon) + self.assertEqual(lensbar.focused_lens_icon, "") def test_category_header_keynav_autoscroll(self): - """Test that the dash autoscroll when a category header gets - the focus. - """ - self.dash.ensure_hidden() - self.dash.reveal_application_lens() - - kb = Keyboard() - mouse = Mouse() - - # Expand the first category - kb.press_and_release("Down") - kb.press_and_release("Enter") - category = self.dash.get_focused_category() - - # Get the geometry of that category header. - x = category['header-x'] - y = category['header-y'] - - # Manually scroll the dash. - mouse.move(x, y, True) - mouse.click(5) - mouse.click(5) - mouse.click(5) - - cached_x = x - cached_y = y - - # Focus the search bar with the mouse - x, y, w, h = self.dash.get_searchbar_geometry() - mouse.move(x+100, y+h/2, True) - mouse.click() - sleep(2) - - # Then focus again the first category header - kb.press_and_release("Down") - kb.press_and_release("Enter") - category = self.dash.get_focused_category() - x = category['header-x'] - y = category['header-y'] - - # Make sure the dash autoscroll - self.assertEqual(x, cached_x) - self.assertEqual(y, cached_y) + """Test that the dash autoscroll when a category header gets + the focus. + """ + self.dash.ensure_hidden() + self.dash.reveal_application_lens() + app_lens = self.dash.get_current_lens() + + kb = Keyboard() + mouse = Mouse() + + # Expand the first category + kb.press_and_release("Down") + kb.press_and_release("Enter") + category = app_lens.get_focused_category() + + # Get the geometry of that category header. + x = category.header_x + y = category.header_y + w = category.header_width + + # Manually scroll the dash. + mouse.move(x+w+10, y+w+10, True) + mouse.click(5) + mouse.click(5) + mouse.click(5) + + cached_y = y + + # Focus the search bar with the mouse + searchbar = self.dash.get_searchbar() + mouse.move(searchbar.x + 100, + searchbar.y + searchbar.height / 2, + True) + mouse.click() + sleep(2) + + # Then focus again the first category header + kb.press_and_release("Down") + kb.press_and_release("Enter") + category = app_lens.get_focused_category() + y = category.header_y + + # Make sure the dash autoscroll + self.assertTrue(abs(y - cached_y) < 30) def test_category_header_keynav(self): - """ This test makes sure that: - 1. A category header can get the focus. - 2. A category header stays highlight when it loses the focus - and mouse is close to it (but not inside). - """ - self.dash.ensure_hidden() - self.dash.reveal_application_lens() + """ This test makes sure that: + 1. A category header can get the focus. + 2. A category header stays highlight when it loses the focus + and mouse is close to it (but not inside). + """ + self.dash.ensure_hidden() + self.dash.reveal_application_lens() + + kb = Keyboard() + mouse = Mouse() + + # Make sure that a category have the focus. + kb.press_and_release("Down") + app_lens = self.dash.get_current_lens() + category = app_lens.get_focused_category() + self.assertIsNot(category, None) + + # Make sure that the category is highlighted. + self.assertTrue(category.header_is_highlighted) + + # Get the geometry of that category header. + x = category.header_x + y = category.header_y + w = category.header_width + h = category.header_height + + # Move the mouse close the view, and press down. + mouse.move(x + w + 10, + y + h / 2, + True) + sleep(1) + kb.press_and_release("Down") + sleep(1) + category = app_lens.get_focused_category() + self.assertEqual(category, None) + + def test_cltr_tab(self): + """ This test makes sure that Ctlr + Tab works well.""" + self.dash.ensure_hidden() + self.dash.toggle_reveal() + + kb = Keyboard() + lensbar = self.dash.view.get_lensbar() + + kb.press('Control') + kb.press_and_release('Tab') + kb.release('Control') + lensbar.refresh_state() + self.assertEqual(lensbar.active_lens, u'applications.lens') + + kb.press('Control') + kb.press('Shift') + kb.press_and_release('Tab') + kb.release('Control') + kb.release('Shift') + + lensbar.refresh_state() + self.assertEqual(lensbar.active_lens, u'home.lens') + + def test_tab(self): + """ This test makes sure that Tab works well.""" + self.dash.ensure_hidden() + self.dash.reveal_application_lens() + app_lens = self.dash.get_current_lens() + + kb = Keyboard() + + for i in range(app_lens.get_num_visible_categories()): + kb.press_and_release('Tab') + category = app_lens.get_focused_category() + self.assertIsNot(category, None) + + kb.press_and_release('Tab') + searchbar = self.dash.get_searchbar() + self.assertTrue(searchbar.expander_has_focus) + + if not searchbar.showing_filters: + kb.press_and_release('Enter') + searchbar.refresh_state() + self.assertTrue(searchbar.showing_filters) + + filter_bar = app_lens.get_filterbar() + for i in range(filter_bar.get_num_filters()): + kb.press_and_release('Tab') + new_focused_filter = filter_bar.get_focused_filter() + self.assertIsNotNone(new_focused_filter) + + kb.press_and_release('Tab') + category = app_lens.get_focused_category() + self.assertIsNot(category, None) - kb = Keyboard() - mouse = Mouse() - # Make sure that a category have the focus. - kb.press_and_release("Down") - category = self.dash.get_focused_category() - self.assertIsNot(category, None) +class DashClipboardTests(AutopilotTestCase): + """Test the Unity clipboard""" - # Make sure that the category is highlighted. - self.assertTrue(category['header-is-highlighted'], True) + def setUp(self): + super(DashClipboardTests, self).setUp() + self.dash = Dash() - # Get the geometry of that category header. - x = category['header-x'] - y = category['header-y'] - w = category['header-width'] - h = category['header-height'] + def tearDown(self): + super(DashClipboardTests, self).tearDown() + self.dash.ensure_hidden() - # Move the mouse close the view, and press down. - mouse.move(x+w+10, y+h/2, True) - sleep(1) - kb.press_and_release("Down") + def test_ctrl_a(self): + """ This test if ctrl+a selects all text """ + self.dash.ensure_hidden() + self.dash.toggle_reveal() - category = self.dash.get_focused_category() - self.assertEqual(category, None) + kb = Keyboard(); + kb.type("SelectAll") + sleep(1) + kb.press_and_release("Ctrl+a") + kb.press_and_release("Delete") + + searchbar = self.dash.get_searchbar() + self.assertEqual(searchbar.search_string, u'') + + def test_ctrl_c(self): + """ This test if ctrl+c copies text into the clipboard """ + self.dash.ensure_hidden() + self.dash.toggle_reveal() + + kb = Keyboard(); + kb.type("Copy") + sleep(1) + kb.press_and_release("Ctrl+a") + kb.press_and_release("Ctrl+c") + cb = Clipboard(selection="CLIPBOARD") + + searchbar = self.dash.get_searchbar() + self.assertEqual(searchbar.search_string, cb.wait_for_text()) + + def test_ctrl_x(self): + """ This test if ctrl+x deletes all text and copys it """ + self.dash.ensure_hidden() + self.dash.toggle_reveal() + + kb = Keyboard(); + kb.type("Cut") + sleep(1) + + kb.press_and_release("Ctrl+a") + kb.press_and_release("Ctrl+x") + sleep(1) + + searchbar = self.dash.get_searchbar() + self.assertEqual(searchbar.search_string, u'') + + cb = Clipboard(selection="CLIPBOARD") + self.assertEqual(cb.wait_for_text(), u'Cut') + + def test_ctrl_c_v(self): + """ This test if ctrl+c and ctrl+v copies and pastes text""" + self.dash.ensure_hidden() + self.dash.toggle_reveal() + + kb = Keyboard(); + kb.type("CopyPaste") + sleep(1) + + kb.press_and_release("Ctrl+a") + kb.press_and_release("Ctrl+c") + kb.press_and_release("Ctrl+v") + kb.press_and_release("Ctrl+v") + + searchbar = self.dash.get_searchbar() + self.assertEqual(searchbar.search_string, u'CopyPasteCopyPaste') + + def test_ctrl_x_v(self): + """ This test if ctrl+x and ctrl+v cuts and pastes text""" + self.dash.ensure_hidden() + self.dash.toggle_reveal() + + kb = Keyboard(); + kb.type("CutPaste") + sleep(1) + + kb.press_and_release("Ctrl+a") + kb.press_and_release("Ctrl+x") + kb.press_and_release("Ctrl+v") + kb.press_and_release("Ctrl+v") + + searchbar = self.dash.get_searchbar() + self.assertEqual(searchbar.search_string, u'CutPasteCutPaste') + + +class DashKeyboardFocusTests(AutopilotTestCase): + """Tests that keyboard focus works.""" + + def setUp(self): + super(DashKeyboardFocusTests, self).setUp() + self.dash = Dash() + + def tearDown(self): + super(DashKeyboardFocusTests, self).tearDown() + self.dash.ensure_hidden() + + def test_filterbar_expansion_leaves_kb_focus(self): + """Expanding or collapsing the filterbar must keave keyboard focus in the + search bar. + """ + self.dash.reveal_application_lens() + filter_bar = self.dash.get_current_lens().get_filterbar() + filter_bar.ensure_collapsed() + + kb = Keyboard() + kb.type("hello") + filter_bar.ensure_expanded() + kb.type(" world") + searchbar = self.dash.get_searchbar() + self.assertEqual("hello world", searchbar.search_string) diff --git a/tests/autopilot/autopilot/tests/test_hud.py b/tests/autopilot/autopilot/tests/test_hud.py index 07e0e2b18..4c07cabdd 100644 --- a/tests/autopilot/autopilot/tests/test_hud.py +++ b/tests/autopilot/autopilot/tests/test_hud.py @@ -6,37 +6,163 @@ # under the terms of the GNU General Public License version 3, as published # by the Free Software Foundation. +from testtools.matchers import Equals, LessThan from time import sleep from autopilot.emulators.unity.hud import HudController -from autopilot.emulators.X11 import Keyboard from autopilot.tests import AutopilotTestCase -from autopilot.glibrunner import GlibRunner class HudTests(AutopilotTestCase): - run_test_with = GlibRunner + def setUp(self): + super(HudTests, self).setUp() + self.hud = self.get_hud_controller() - def test_reveal_hud_with_no_apps(self): - """Hud must show even with no visible applications.""" + def tearDown(self): + self.hud.ensure_hidden() + super(HudTests, self).tearDown() + + def get_hud_controller(self): controllers = HudController.get_all_instances() self.assertEqual(1, len(controllers)) - controller = controllers[0] + return controllers[0] + + def get_num_active_launcher_icons(self): + num_active = 0 + for icon in self.launcher.model.get_launcher_icons(): + if icon.quirk_active and icon.quirk_visible: + num_active += 1 + return num_active + + def test_initially_hidden(self): + self.assertFalse(self.hud.is_visible()) + + def test_reveal_hud(self): + self.hud.toggle_reveal() + self.assertTrue(self.hud.is_visible()) + + def test_no_initial_values(self): + self.hud.toggle_reveal() + self.assertThat(self.hud.num_buttons, Equals(0)) + self.assertThat(self.hud.selected_button, Equals(0)) + + def test_check_a_values(self): + self.hud.toggle_reveal() + self.keyboard.type('a') + self.assertThat(self.hud.num_buttons, Equals(5)) + self.assertThat(self.hud.selected_button, Equals(1)) + + def test_up_down_arrows(self): + self.hud.toggle_reveal() + self.keyboard.type('a') + self.keyboard.press_and_release('Down') + self.assertThat(self.hud.selected_button, Equals(2)) + self.keyboard.press_and_release('Down') + self.assertThat(self.hud.selected_button, Equals(3)) + self.keyboard.press_and_release('Down') + self.assertThat(self.hud.selected_button, Equals(4)) + self.keyboard.press_and_release('Down') + self.assertThat(self.hud.selected_button, Equals(5)) + # Down again stays on 5. + self.keyboard.press_and_release('Down') + self.assertThat(self.hud.selected_button, Equals(5)) + self.keyboard.press_and_release('Up') + self.assertThat(self.hud.selected_button, Equals(4)) + self.keyboard.press_and_release('Up') + self.assertThat(self.hud.selected_button, Equals(3)) + self.keyboard.press_and_release('Up') + self.assertThat(self.hud.selected_button, Equals(2)) + self.keyboard.press_and_release('Up') + self.assertThat(self.hud.selected_button, Equals(1)) + # Up again stays on 1. + self.keyboard.press_and_release('Up') + self.assertThat(self.hud.selected_button, Equals(1)) + + def test_slow_tap_not_reveal_hud(self): + self.hud.toggle_reveal(tap_delay=0.3) + self.assertFalse(self.hud.is_visible()) + + def test_alt_f4_doesnt_show_hud(self): + self.start_app('Calculator') + sleep(1) + # Do a very fast Alt+F4 + self.keyboard.press_and_release("Alt+F4", 0.05) + self.assertFalse(self.hud.is_visible()) + + def test_reveal_hud_with_no_apps(self): + """Hud must show even with no visible applications.""" + self.keyboard.press_and_release("Ctrl+Alt+d") + self.addCleanup(self.keyboard.press_and_release, "Ctrl+Alt+d") + sleep(1) + + self.hud.toggle_reveal() + sleep(1) + self.assertTrue(self.hud.is_visible()) - self.assertFalse(controller.visible) - kb = Keyboard() - kb.press_and_release("Ctrl+Alt+d") - self.addCleanup(kb.press_and_release, "Ctrl+Alt+d") + self.hud.toggle_reveal() sleep(1) + self.assertFalse(self.hud.is_visible()) + + def test_multiple_hud_reveal_does_not_break_launcher(self): + """Multiple Hud reveals must not cause the launcher to set multiple + apps as active. - # we need a *fast* keypress to reveal the hud: - kb.press_and_release("Alt", delay=0.1) + """ + # We need an app to switch to: + self.start_app('Character Map') + # We need an application to play with - I'll use the calculator. + self.start_app('Calculator') sleep(1) - controller.refresh_state() - self.assertTrue(controller.visible) - kb.press_and_release("Alt", delay=0.1) + # before we start, make sure there's zero or one active icon: + num_active = self.get_num_active_launcher_icons() + self.assertThat(num_active, LessThan(2), "Invalid number of launcher icons active before test has run!") + + # reveal and hide hud several times over: + for i in range(3): + self.hud.ensure_visible() + sleep(0.5) + self.hud.ensure_hidden() + sleep(0.5) + + # click application icons for running apps in the launcher: + icon = self.launcher.model.get_icon_by_tooltip_text("Character Map") + self.launcher.get_launcher_for_monitor(0).click_launcher_icon(icon) + + # see how many apps are marked as being active: + num_active = self.get_num_active_launcher_icons() + self.assertLessEqual(num_active, 1, "More than one launcher icon active after test has run!") + + def test_restore_focus(self): + """Ensures that once the hud is dismissed, the same application + that was focused before hud invocation is refocused + """ + self.start_app("Calculator") + calc = self.get_app_instances("Calculator") + self.assertThat(len(calc), Equals(1)) + calc = calc[0] + + # first ensure that the application has started and is focused + self.assertEqual(calc.is_active, True) + + self.hud.toggle_reveal() + sleep(1) + self.hud.toggle_reveal() sleep(1) - controller.refresh_state() - self.assertFalse(controller.visible) + + # again ensure that the application we started is focused + self.assertEqual(calc.is_active, True) + + #test return + self.hud.toggle_reveal() + sleep(1) + + #test return + self.hud.toggle_reveal() + sleep(1) + self.keyboard.press_and_release('Return') + sleep(1) + + self.assertEqual(calc.is_active, True) + diff --git a/tests/autopilot/autopilot/tests/test_ibus.py b/tests/autopilot/autopilot/tests/test_ibus.py new file mode 100644 index 000000000..c2d6b0b37 --- /dev/null +++ b/tests/autopilot/autopilot/tests/test_ibus.py @@ -0,0 +1,120 @@ +# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*- +# Copyright 2012 Canonical +# Author: Thomi Richards, Martin Mrazik +# +# This program is free software: you can redistribute it and/or modify it +# under the terms of the GNU General Public License version 3, as published +# by the Free Software Foundation. + +"Tests to ensure unity is compatible with ibus input method." + +from autopilot.emulators.ibus import ( + set_active_engines, + get_available_input_engines, + ) +from autopilot.emulators.unity.dash import Dash +from autopilot.emulators.X11 import Keyboard +from autopilot.tests import AutopilotTestCase + +from time import sleep + +class IBusTests(AutopilotTestCase): + """Base class for IBus tests.""" + + def setUp(self): + super(IBusTests, self).setUp() + self.kb = Keyboard() + self.dash = Dash() + self._old_engines = None + + def tearDown(self): + if self._old_engines is not None: + set_active_engines(self._old_engines) + super(IBusTests, self).tearDown() + + def activate_input_engine_or_skip(self, engine_name): + available_engines = get_available_input_engines() + if engine_name in available_engines: + self._old_engines = set_active_engines([engine_name]) + else: + self.skipTest("This test requires the '%s' engine to be installed." % (engine_name)) + + def activate_ibus(self): + # it would be nice to be able to tell if it's currently active or not. + self.kb.press_and_release('Ctrl+Space') + + def deactivate_ibus(self): + # it would be nice to be able to tell if it's currently active or not. + self.kb.press_and_release('Ctrl+Space') + + +class IBusTestsPinyin(IBusTests): + """Tests for the Pinyin(Chinese) input engine.""" + + scenarios = [ + ('basic', {'input': 'abc1', 'result': u'\u963f\u5e03\u4ece'}), + ('photo', {'input': 'zhaopian ', 'result': u'\u7167\u7247'}), + ('internet', {'input': 'hulianwang ', 'result': u'\u4e92\u8054\u7f51'}), + ('disk', {'input': 'cipan ', 'result': u'\u78c1\u76d8'}), + ('disk_management', {'input': 'cipan guanli ', 'result': u'\u78c1\u76d8\u7ba1\u7406'}), + ] + + def test_simple_input(self): + self.activate_input_engine_or_skip("pinyin") + self.dash.ensure_visible() + sleep(0.5) + self.activate_ibus() + sleep(0.5) + self.kb.type(self.input) + dash_search_string = self.dash.get_searchbar().search_string + self.deactivate_ibus() + self.dash.ensure_hidden() + + self.assertEqual(self.result, dash_search_string) + + +class IBusTestsHangul(IBusTests): + """Tests for the Hangul(Korean) input engine.""" + + scenarios = [ + ('transmission', {'input': 'xmfostmaltus ', 'result': u'\ud2b8\ub79c\uc2a4\ubbf8\uc158 '}), + ('social', {'input': 'httuf ', 'result': u'\uc18c\uc15c '}), + ('document', {'input': 'anstj ', 'result': u'\ubb38\uc11c '}), + ] + + def test_simple_input(self): + self.activate_input_engine_or_skip("hangul") + self.dash.ensure_visible() + sleep(0.5) + self.activate_ibus() + sleep(0.5) + self.kb.type(self.input) + dash_search_string = self.dash.get_searchbar().search_string + self.deactivate_ibus() + self.dash.ensure_hidden() + + self.assertEqual(self.result, dash_search_string) + + +class IBusTestsAnthy(IBusTests): + """Tests for the Anthy(Japanese) input engine.""" + + scenarios = [ + ('system', {'input': 'shisutemu ', 'result': u'\u30b7\u30b9\u30c6\u30e0'}), + ('game', {'input': 'ge-mu ', 'result': u'\u30b2\u30fc\u30e0'}), + ('user', {'input': 'yu-za- ', 'result': u'\u30e6\u30fc\u30b6\u30fc'}), + ] + + def test_simple_input(self): + self.activate_input_engine_or_skip("anthy") + self.dash.ensure_visible() + sleep(0.5) + self.activate_ibus() + sleep(0.5) + self.kb.type(self.input) + self.kb.press_and_release("Ctrl+j") + dash_search_string = self.dash.get_searchbar().search_string + self.deactivate_ibus() + self.dash.ensure_hidden() + + self.assertEqual(self.result, dash_search_string) diff --git a/tests/autopilot/autopilot/tests/test_invisible_windows.py b/tests/autopilot/autopilot/tests/test_invisible_windows.py index e34a5e136..df48d094f 100644 --- a/tests/autopilot/autopilot/tests/test_invisible_windows.py +++ b/tests/autopilot/autopilot/tests/test_invisible_windows.py @@ -15,7 +15,6 @@ from testtools import TestCase from time import sleep from autopilot.utilities import make_window_skip_taskbar -from autopilot.emulators.unity.launcher import Launcher from autopilot.emulators.unity.switcher import Switcher from autopilot.emulators.bamf import Bamf @@ -28,16 +27,17 @@ class InvisibleWindowTests(TestCase): in the launcher or the switcher. """ + self.skipTest("This test needs to be rewritten. We don't support changing window states after they've been mapped.") + b = Bamf() - self.assertFalse(b.application_is_running('Calculator')) + self.assertFalse(self.app_is_running('Calculator')) b.launch_application("gcalctool.desktop") self.addCleanup(call, ["killall", "gcalctool"]) sleep(1) switcher = Switcher() - launcher = Launcher() # calculator should be in both launcher AND switcher: - icon_names = [i.tooltip_text for i in launcher.get_launcher_icons()] + icon_names = [i.tooltip_text for i in self.launcher.model.get_launcher_icons()] self.assertIn('Calculator', icon_names) switcher.initiate() @@ -54,7 +54,7 @@ class InvisibleWindowTests(TestCase): sleep(2) # calculator should now NOT be in both launcher AND switcher: - icon_names = [i.tooltip_text for i in launcher.get_launcher_icons()] + icon_names = [i.tooltip_text for i in self.launcher.model.get_launcher_icons()] self.assertNotIn('Calculator', icon_names) switcher.initiate() @@ -64,14 +64,16 @@ class InvisibleWindowTests(TestCase): def test_pinned_invisible_window(self): """Test behavior of an app with an invisible window that's pinned to the launher.""" + + self.skipTest("This test needs to be rewritten. We don't support changing window states after they've been mapped.") + b = Bamf() - self.assertFalse(b.application_is_running('Calculator')) + self.assertFalse(b.app_is_running('Calculator')) b.launch_application("gcalctool.desktop") self.addCleanup(call, ["killall", "gcalctool"]) # need to pin the app to the launcher - this could be tricky. - launcher = Launcher() - launcher.reveal_launcher(0) - icons = launcher.get_launcher_icons() + self.launcher.get_launcher_for_monitor(0).reveal_launcher() + icons = self.launcher.model.get_launcher_icons() # launcher.grab_switcher() calc_icon = None for icon in icons: @@ -80,8 +82,8 @@ class InvisibleWindowTests(TestCase): break self.assertIsNotNone(calc_icon, "Could not find calculator in launcher.") - launcher.lock_to_launcher(calc_icon) - self.addCleanup(launcher.unlock_from_launcher, calc_icon) + self.launcher.get_launcher_for_monitor(0).lock_to_launcher(calc_icon) + self.addCleanup(self.launcher.get_launcher_for_monitor(0).unlock_from_launcher, calc_icon) # make calc window skip the taskbar: apps = b.get_running_applications_by_title('Calculator') @@ -91,7 +93,7 @@ class InvisibleWindowTests(TestCase): make_window_skip_taskbar(windows[0].x_win) # clicking on launcher icon should start a new instance: - launcher.click_launcher_icon(calc_icon) + self.launcher.get_launcher_for_monitor(0).click_launcher_icon(calc_icon) sleep(1) # should now be one app with two windows: diff --git a/tests/autopilot/autopilot/tests/test_launcher.py b/tests/autopilot/autopilot/tests/test_launcher.py index 6577541bb..b38102bc8 100644 --- a/tests/autopilot/autopilot/tests/test_launcher.py +++ b/tests/autopilot/autopilot/tests/test_launcher.py @@ -1,130 +1,404 @@ -from testtools.matchers import Equals -from testtools.matchers import LessThan +# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*- +# Copyright 2012 Canonical +# Authors: Thomi Richards, +# Marco Trevisan (Treviño) +# +# This program is free software: you can redistribute it and/or modify it +# under the terms of the GNU General Public License version 3, as published +# by the Free Software Foundation. + +import logging +from testtools.matchers import Equals, LessThan, GreaterThan +from time import sleep from autopilot.tests import AutopilotTestCase -from autopilot.emulators.unity.launcher import Launcher -from autopilot.glibrunner import GlibRunner +from autopilot.emulators.X11 import ScreenGeometry -from time import sleep +logger = logging.getLogger(__name__) + +def _make_scenarios(): + """Make scenarios for launcher test cases based on the number of configured + monitors. + """ + screen_geometry = ScreenGeometry() + num_monitors = screen_geometry.get_num_monitors() + if num_monitors == 1: + return [('Single Monitor', {'launcher_num': 0})] + else: + return [('Monitor %d' % (i), {'launcher_num': i}) for i in range(num_monitors)] + + +class ScenariodLauncherTests(AutopilotTestCase): + """A base class for all launcher tests that want to use scenarios to run on + each launcher (for multi-monitor setups). + """ + scenarios = _make_scenarios() -class LauncherTests(AutopilotTestCase): + def get_launcher(self): + """Get the launcher for the current scenario.""" + return self.launcher.get_launcher_for_monitor(self.launcher_num) + + +class LauncherTests(ScenariodLauncherTests): """Test the launcher.""" - run_test_with = GlibRunner def setUp(self): super(LauncherTests, self).setUp() - self.server = Launcher() + sleep(1) - def test_launcher_switcher_ungrabbed(self): - """Tests basic key nav integration without keyboard grabs.""" + def test_launcher_switcher_starts_at_index_zero(self): + """Test that starting the Launcher switcher puts the keyboard focus on item 0.""" sleep(.5) + launcher_instance = self.get_launcher() + launcher_instance.start_switcher() + self.addCleanup(launcher_instance.end_switcher, True) + sleep(.5) + + self.assertThat(self.launcher.key_nav_is_active, Equals(True)) + self.assertThat(self.launcher.key_nav_is_grabbed, Equals(False)) + self.assertThat(self.launcher.key_nav_selection, Equals(0)) - self.server.start_switcher() + def test_launcher_switcher_end_works(self): + """Test that ending the launcher switcher actually works.""" sleep(.5) + launcher_instance = self.get_launcher() + launcher_instance.start_switcher() + sleep(.5) + launcher_instance.end_switcher(cancel=True) + sleep(.5) + self.assertThat(self.launcher.key_nav_is_active, Equals(False)) - self.assertThat(self.server.key_nav_is_active(), Equals(True)) - self.assertThat(self.server.key_nav_is_grabbed(), Equals(False)) - self.assertThat(self.server.key_nav_selection(), Equals(0)) + def test_launcher_switcher_next_works(self): + """Moving to the next launcher item while switcher is activated must work.""" + sleep(.5) + launcher_instance = self.get_launcher() + launcher_instance.start_switcher() + self.addCleanup(launcher_instance.end_switcher, True) + sleep(.5) - self.server.switcher_next() + launcher_instance.switcher_next() sleep(.5) - self.assertThat(0, LessThan(self.server.key_nav_selection())) + self.assertThat(self.launcher.key_nav_selection, Equals(1)) - self.server.switcher_prev() + def test_launcher_switcher_prev_works(self): + """Moving to the previous launcher item while switcher is activated must work.""" + sleep(.5) + launcher_instance = self.get_launcher() + launcher_instance.start_switcher() + self.addCleanup(launcher_instance.end_switcher, True) sleep(.5) - self.assertThat(self.server.key_nav_selection(), Equals(0)) - self.server.end_switcher(True) + launcher_instance.switcher_next() sleep(.5) - self.assertThat(self.server.key_nav_is_active(), Equals(False)) + launcher_instance.switcher_prev() + self.assertThat(self.launcher.key_nav_selection, Equals(0)) - def test_launcher_switcher_grabbed(self): - """Tests basic key nav integration via keyboard grab.""" + def test_launcher_switcher_next_doesnt_show_shortcuts(self): + """Moving forward in launcher switcher must not show launcher shortcuts.""" + sleep(.5) + launcher_instance = self.get_launcher() + launcher_instance.start_switcher() + self.addCleanup(launcher_instance.end_switcher, True) sleep(.5) + launcher_instance.switcher_next() + sleep(2) + self.assertThat(launcher_instance.are_shortcuts_showing(), Equals(False)) - self.server.grab_switcher() + def test_launcher_switcher_prev_doesnt_show_shortcuts(self): + """Moving backward in launcher switcher must not show launcher shortcuts.""" sleep(.5) + launcher_instance = self.get_launcher() + launcher_instance.start_switcher() + self.addCleanup(launcher_instance.end_switcher, True) + sleep(.5) + launcher_instance.switcher_next() + sleep(2) + launcher_instance.switcher_prev() + sleep(2) + self.assertThat(launcher_instance.are_shortcuts_showing(), Equals(False)) - self.assertThat(self.server.key_nav_is_active(), Equals(True)) - self.assertThat(self.server.key_nav_is_grabbed(), Equals(True)) - self.assertThat(self.server.key_nav_selection(), Equals(0)) - self.server.switcher_next() + def test_launcher_switcher_cycling_forward(self): + """Launcher Switcher must loop through icons when cycling forwards""" + sleep(.5) + launcher_instance = self.get_launcher() + launcher_instance.start_switcher() + self.addCleanup(launcher_instance.end_switcher, True) sleep(.5) - self.assertThat(0, LessThan(self.server.key_nav_selection())) - self.server.switcher_prev() + prev_icon = 0 + num_icons = self.launcher.model.num_launcher_icons() + logger.info("This launcher has %d icons", num_icons) + for icon in range(1, num_icons): + launcher_instance.switcher_next() + sleep(.25) + # FIXME We can't directly check for selection/icon number equalty + # since the launcher model also contains "hidden" icons that aren't + # shown, so the selection index can increment by more than 1. + self.assertThat(prev_icon, LessThan(self.launcher.key_nav_selection)) + prev_icon = self.launcher.key_nav_selection + + sleep(.5) + launcher_instance.switcher_next() + self.assertThat(self.launcher.key_nav_selection, Equals(0)) + + def test_launcher_switcher_cycling_backward(self): + """Launcher Switcher must loop through icons when cycling backwards""" + sleep(.5) + launcher_instance = self.get_launcher() + launcher_instance.start_switcher() + self.addCleanup(launcher_instance.end_switcher, True) sleep(.5) - self.assertThat(self.server.key_nav_selection(), Equals(0)) - self.server.end_switcher(True) + launcher_instance.switcher_prev() + # FIXME We can't directly check for self.launcher.num_launcher_icons - 1 + self.assertThat(self.launcher.key_nav_selection, GreaterThan(1)) + + def test_launcher_keyboard_reveal_works(self): + """Revealing launcher with keyboard must work.""" + launcher_instance = self.get_launcher() + launcher_instance.keyboard_reveal_launcher() + self.addCleanup(launcher_instance.keyboard_unreveal_launcher) + sleep(0.5) + self.assertThat(launcher_instance.is_showing(), Equals(True)) + + def test_launcher_keyboard_reveal_shows_shortcut_hints(self): + """Launcher icons must show shortcut hints after revealing with keyboard.""" + launcher_instance = self.get_launcher() + launcher_instance.move_mouse_to_right_of_launcher() + launcher_instance.keyboard_reveal_launcher() + self.addCleanup(launcher_instance.keyboard_unreveal_launcher) + sleep(1) + + self.assertThat(launcher_instance.are_shortcuts_showing(), Equals(True)) + + def test_launcher_switcher_keeps_shorcuts(self): + """Initiating launcher switcher after showing shortcuts must not hide shortcuts""" + sleep(.5) + launcher_instance = self.get_launcher() + launcher_instance.move_mouse_to_right_of_launcher() + launcher_instance.keyboard_reveal_launcher() + self.addCleanup(launcher_instance.keyboard_unreveal_launcher) + sleep(1) + + launcher_instance.start_switcher() + self.addCleanup(launcher_instance.end_switcher, True) + sleep(.5) + + self.assertThat(self.launcher.key_nav_is_active, Equals(True)) + self.assertThat(launcher_instance.are_shortcuts_showing(), Equals(True)) + + def test_launcher_switcher_next_and_prev_keep_shortcuts(self): + """Launcher switcher next and prev actions must keep shortcuts after they've been shown.""" + sleep(.5) + launcher_instance = self.get_launcher() + launcher_instance.move_mouse_to_right_of_launcher() + launcher_instance.keyboard_reveal_launcher() + self.addCleanup(launcher_instance.keyboard_unreveal_launcher) + sleep(1) + + launcher_instance.start_switcher() + self.addCleanup(launcher_instance.end_switcher, True) sleep(.5) - self.assertThat(self.server.key_nav_is_active(), Equals(False)) - def test_launcher_switcher_quicklist_interaction(self): - """Tests that the key nav opens and closes quicklists properly and regrabs afterwards.""" - self.server.move_mouse_to_right_of_launcher(0) + launcher_instance.switcher_next() sleep(.5) + self.assertThat(launcher_instance.are_shortcuts_showing(), Equals(True)) - self.server.grab_switcher() + launcher_instance.switcher_prev() sleep(.5) + self.assertThat(launcher_instance.are_shortcuts_showing(), Equals(True)) - self.assertThat(self.server.key_nav_is_active(), Equals(True)) - self.assertThat(self.server.key_nav_is_grabbed(), Equals(True)) + def test_launcher_switcher_ungrabbed_using_shorcuts(self): + """Using some other shortcut while switcher is active must cancel switcher.""" + sleep(.5) - self.server.switcher_next() + launcher_instance = self.get_launcher() + launcher_instance.move_mouse_to_right_of_launcher() + launcher_instance.keyboard_reveal_launcher() + self.addCleanup(launcher_instance.keyboard_unreveal_launcher) + sleep(1) + launcher_instance.start_switcher() + self.addCleanup(launcher_instance.end_switcher, True) sleep(.5) - self.server.switcher_enter_quicklist() + self.keyboard.press_and_release("s") + sleep(.25) + self.keyboard.press_and_release("Escape") + sleep(.25) + + self.assertThat(self.launcher.key_nav_is_active, Equals(False)) + + def test_launcher_keynav_initiate_works(self): + """Tests we can initiate keyboard navigation on the launcher.""" + launcher_instance = self.get_launcher() sleep(.5) - self.assertThat(self.server.is_quicklist_open(0), Equals(True)) - self.server.switcher_exit_quicklist() + launcher_instance.grab_switcher() + self.addCleanup(launcher_instance.end_switcher, True) sleep(.5) - self.assertThat(self.server.is_quicklist_open(0), Equals(False)) - self.assertThat(self.server.key_nav_is_active(), Equals(True)) - self.assertThat(self.server.key_nav_is_grabbed(), Equals(True)) + self.assertThat(self.launcher.key_nav_is_active, Equals(True)) + self.assertThat(self.launcher.key_nav_is_grabbed, Equals(True)) - self.server.end_switcher(True) + def test_launcher_keynav_end_works(self): + """Test that we can exit keynav mode.""" + launcher_instance = self.get_launcher() sleep(.5) - self.assertThat(self.server.key_nav_is_active(), Equals(False)) + launcher_instance.grab_switcher() + sleep(.5) + launcher_instance.end_switcher(cancel=True) + self.assertThat(self.launcher.key_nav_is_active, Equals(False)) + self.assertThat(self.launcher.key_nav_is_grabbed, Equals(False)) - def test_reveal_on_mouse_to_edge(self): - """Tests reveal of launchers by mouse pressure.""" - # XXX: re-enable test when launcher reeal behavior is no longer resolution-dependant. - self.skipTest("Launcher reveal behavior is resolution dependant.") + def test_launcher_keynav_starts_at_index_zero(self): + """Test keynav mode starts at index 0.""" + launcher_instance = self.get_launcher() + sleep(.5) + launcher_instance.grab_switcher() + self.addCleanup(launcher_instance.end_switcher, True) + sleep(.5) + + self.assertThat(self.launcher.key_nav_selection, Equals(0)) - num_launchers = self.server.num_launchers() + def test_launcher_keynav_forward_works(self): + """Must be able to move forwards while in keynav mode.""" + launcher_instance = self.get_launcher() + sleep(.5) + launcher_instance.grab_switcher() + self.addCleanup(launcher_instance.end_switcher, True) + sleep(.5) + launcher_instance.switcher_next() + sleep(.5) + self.assertThat(self.launcher.key_nav_selection, Equals(1)) - for x in range(num_launchers): - self.server.move_mouse_to_right_of_launcher(x) - self.server.reveal_launcher(x) - self.assertThat(self.server.is_showing(x), Equals(True)) + def test_launcher_keynav_prev_works(self): + """Must be able to move backwards while in keynav mode.""" + launcher_instance = self.get_launcher() + sleep(.5) + launcher_instance.grab_switcher() + self.addCleanup(launcher_instance.end_switcher, True) + sleep(.5) + launcher_instance.switcher_next() + sleep(.5) + launcher_instance.switcher_prev() + sleep(.5) + self.assertThat(self.launcher.key_nav_selection, Equals(0)) + + def test_launcher_keynav_cycling_forward(self): + """Launcher keynav must loop through icons when cycling forwards""" + launcher_instance = self.get_launcher() + sleep(.5) + launcher_instance.grab_switcher() + self.addCleanup(launcher_instance.end_switcher, True) + sleep(.25) + + prev_icon = 0 + for icon in range(1, self.launcher.model.num_launcher_icons()): + launcher_instance.switcher_next() + sleep(.25) + # FIXME We can't directly check for selection/icon number equalty + # since the launcher model also contains "hidden" icons that aren't + # shown, so the selection index can increment by more than 1. + self.assertThat(prev_icon, LessThan(self.launcher.key_nav_selection)) + prev_icon = self.launcher.key_nav_selection + + sleep(.5) + launcher_instance.switcher_next() + self.assertThat(self.launcher.key_nav_selection, Equals(0)) + + def test_launcher_keynav_cycling_backward(self): + """Launcher keynav must loop through icons when cycling backwards""" + launcher_instance = self.get_launcher() + sleep(.5) + launcher_instance.grab_switcher() + self.addCleanup(launcher_instance.end_switcher, True) + sleep(.25) + + launcher_instance.switcher_prev() + # FIXME We can't directly check for self.launcher.num_launcher_icons - 1 + self.assertThat(self.launcher.key_nav_selection, GreaterThan(1)) + + def test_launcher_keynav_can_open_quicklist(self): + """Tests that we can open a quicklist from keynav mode.""" + launcher_instance = self.get_launcher() + launcher_instance.move_mouse_to_right_of_launcher() + sleep(.5) + launcher_instance.grab_switcher() + self.addCleanup(launcher_instance.end_switcher, True) + sleep(.5) + launcher_instance.switcher_next() + sleep(.5) + launcher_instance.switcher_enter_quicklist() + self.addCleanup(launcher_instance.switcher_exit_quicklist) + sleep(.5) + self.assertThat(launcher_instance.is_quicklist_open(), Equals(True)) + + def test_launcher_keynav_can_close_quicklist(self): + """Tests that we can close a quicklist from keynav mode.""" + launcher_instance = self.get_launcher() + launcher_instance.move_mouse_to_right_of_launcher() + sleep(.5) + launcher_instance.grab_switcher() + self.addCleanup(launcher_instance.end_switcher, True) + sleep(.5) + launcher_instance.switcher_next() + sleep(.5) + launcher_instance.switcher_enter_quicklist() + sleep(.5) + launcher_instance.switcher_exit_quicklist() + + self.assertThat(launcher_instance.is_quicklist_open(), Equals(False)) + self.assertThat(self.launcher.key_nav_is_active, Equals(True)) + self.assertThat(self.launcher.key_nav_is_grabbed, Equals(True)) + +class LauncherRevealTests(ScenariodLauncherTests): + """Test the launcher reveal bahavior when in autohide mode.""" + + def setUp(self): + super(LauncherRevealTests, self).setUp() + self.set_unity_option('launcher_hide_mode', True) + sleep(1) + + def test_reveal_on_mouse_to_edge(self): + """Tests reveal of launchers by mouse pressure.""" + launcher_instance = self.get_launcher() + launcher_instance.move_mouse_to_right_of_launcher() + launcher_instance.reveal_launcher() + self.assertThat(launcher_instance.is_showing(), Equals(True)) def test_reveal_with_mouse_under_launcher(self): """Tests that the launcher hides properly if the - mouse is under the launcher when it is revealed.""" - # XXX: re-enable test when launcher reeal behavior is no longer resolution-dependant. - self.skipTest("Launcher reveal behavior is resolution dependant.") + mouse is under the launcher when it is revealed. - num_launchers = self.server.num_launchers() + """ + launcher_instance = self.get_launcher() - for x in range(num_launchers): - self.server.move_mouse_over_launcher(x) - self.server.keyboard_reveal_launcher() - self.server.keyboard_unreveal_launcher() - self.assertThat(self.server.is_showing(x), Equals(False)) + launcher_instance.move_mouse_over_launcher() + launcher_instance.keyboard_reveal_launcher() + launcher_instance.keyboard_unreveal_launcher() + sleep(1) + self.assertThat(launcher_instance.is_showing(), Equals(False)) def test_reveal_does_not_hide_again(self): - """Tests reveal of launchers by mouse pressure to ensure it doesn't automatically hide again.""" - # XXX: re-enable test when launcher reeal behavior is no longer resolution-dependant. - self.skipTest("Launcher reveal behavior is resolution dependant.") - - num_launchers = self.server.num_launchers() - - for x in range(num_launchers): - self.server.move_mouse_to_right_of_launcher(x) - self.server.reveal_launcher(x) - sleep(2) - self.assertThat(self.server.is_showing(x), Equals(True)) + """Tests reveal of launchers by mouse pressure to ensure it doesn't + automatically hide again. + + """ + launcher_instance = self.get_launcher() + + launcher_instance.move_mouse_to_right_of_launcher() + launcher_instance.reveal_launcher() + sleep(2) + self.assertThat(launcher_instance.is_showing(), Equals(True)) + + def test_launcher_does_not_reveal_with_mouse_down(self): + """Launcher must not reveal if have mouse button 1 down.""" + launcher_instance = self.get_launcher() + screens = ScreenGeometry() + + screens.move_mouse_to_monitor(launcher_instance.monitor) + self.mouse.press(1) + launcher_instance.reveal_launcher() + self.assertThat(launcher_instance.is_showing(), Equals(False)) + self.mouse.release(1) diff --git a/tests/autopilot/autopilot/tests/test_quicklist.py b/tests/autopilot/autopilot/tests/test_quicklist.py new file mode 100644 index 000000000..88addf08f --- /dev/null +++ b/tests/autopilot/autopilot/tests/test_quicklist.py @@ -0,0 +1,51 @@ +# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*- +# Copyright 2012 Canonical +# Author: Thomi Richards +# +# This program is free software: you can redistribute it and/or modify it +# under the terms of the GNU General Public License version 3, as published +# by the Free Software Foundation. + +import os.path +from testtools.matchers import Not, Is, Contains +from xdg.DesktopEntry import DesktopEntry + +from autopilot.emulators.unity.quicklist import QuicklistMenuItemLabel +from autopilot.tests import AutopilotTestCase + +class QuicklistActionTests(AutopilotTestCase): + """Tests for quicklist actions.""" + + scenarios = [ + ('remmina', {'app_name': 'Remmina'}), + ] + + def test_quicklist_actions(self): + """Test that all actions present in the destop file are shown in the quicklist.""" + self.start_app(self.app_name) + + # load the desktop file from disk: + desktop_file = os.path.join('/usr/share/applications', + self.KNOWN_APPS[self.app_name]['desktop-file'] + ) + de = DesktopEntry(desktop_file) + # get the launcher icon from the launcher: + launcher_icon = self.launcher.model.get_icon_by_tooltip_text(de.getName()) + self.assertThat(launcher_icon, Not(Is(None))) + + # open the icon quicklist, and get all the text labels: + launcher = self.launcher.get_launcher_for_monitor(0) + launcher.click_launcher_icon(launcher_icon, button=3) + ql = launcher_icon.get_quicklist() + ql_item_texts = [i.text for i in ql.items if type(i) is QuicklistMenuItemLabel] + + # iterate over all the actions from the desktop file, make sure they're + # present in the quicklist texts. + actions = de.getActions() + for action in actions: + key = 'Desktop Action ' + action + self.assertThat(de.content, Contains(key)) + name = de.content[key]['Name'] + self.assertThat(ql_item_texts, Contains(name)) + + diff --git a/tests/autopilot/autopilot/tests/test_showdesktop.py b/tests/autopilot/autopilot/tests/test_showdesktop.py index 6e6803811..da370a598 100644 --- a/tests/autopilot/autopilot/tests/test_showdesktop.py +++ b/tests/autopilot/autopilot/tests/test_showdesktop.py @@ -7,44 +7,34 @@ # by the Free Software Foundation. from time import sleep -from subprocess import call -from autopilot.emulators.bamf import Bamf -from autopilot.emulators.unity.launcher import Launcher from autopilot.emulators.unity.switcher import Switcher -from autopilot.emulators.X11 import Keyboard -from autopilot.glibrunner import GlibRunner from autopilot.tests import AutopilotTestCase class ShowDesktopTests(AutopilotTestCase): """Test the 'Show Desktop' functionality.""" - run_test_with = GlibRunner def setUp(self): super(ShowDesktopTests, self).setUp() - self.addCleanup(Keyboard.cleanup) - self.bamf = Bamf() # we need this to let the unity models update after we shutdown apps # before we start the next test. - sleep(5) + sleep(2) def launch_test_apps(self): """Launch character map and calculator apps.""" - self.bamf.launch_application("gucharmap.desktop") - self.addCleanup(call, ["killall", "gucharmap"]) - self.bamf.launch_application("gcalctool.desktop") - self.addCleanup(call, ["killall", "gcalctool"]) + self.start_app('Character Map') + self.start_app('Calculator') + sleep(1) def test_showdesktop_hides_apps(self): """Show Desktop keyboard shortcut must hide applications.""" self.launch_test_apps() # show desktop, verify all windows are hidden: - kb = Keyboard() - kb.press_and_release('Control+Alt+d') - self.addCleanup(kb.press_and_release, keys='Control+Alt+d') - sleep(1) + self.keyboard.press_and_release('Control+Alt+d') + self.addCleanup(self.keyboard.press_and_release, keys='Control+Alt+d') + sleep(3) open_wins = self.bamf.get_open_windows() self.assertGreaterEqual(len(open_wins), 2) for win in open_wins: @@ -56,9 +46,8 @@ class ShowDesktopTests(AutopilotTestCase): self.launch_test_apps() # show desktop, verify all windows are hidden: - kb = Keyboard() - kb.press_and_release('Control+Alt+d') - sleep(1) + self.keyboard.press_and_release('Control+Alt+d') + sleep(3) open_wins = self.bamf.get_open_windows() self.assertGreaterEqual(len(open_wins), 2) for win in open_wins: @@ -66,8 +55,8 @@ class ShowDesktopTests(AutopilotTestCase): self.assertTrue(win.is_hidden, "Window '%s' is not hidden after show desktop activated." % (win.title)) # un-show desktop, verify all windows are shown: - kb.press_and_release('Control+Alt+d') - sleep(1) + self.keyboard.press_and_release('Control+Alt+d') + sleep(3) for win in self.bamf.get_open_windows(): self.assertTrue(win.is_valid) self.assertFalse(win.is_hidden, "Window '%s' is shown after show desktop deactivated." % (win.title)) @@ -77,9 +66,8 @@ class ShowDesktopTests(AutopilotTestCase): self.launch_test_apps() # show desktop, verify all windows are hidden: - kb = Keyboard() - kb.press_and_release('Control+Alt+d') - sleep(1) + self.keyboard.press_and_release('Control+Alt+d') + sleep(3) open_wins = self.bamf.get_open_windows() self.assertGreaterEqual(len(open_wins), 2) for win in open_wins: @@ -87,17 +75,13 @@ class ShowDesktopTests(AutopilotTestCase): self.assertTrue(win.is_hidden, "Window '%s' is not hidden after show desktop activated." % (win.title)) # We'll un-minimise the character map - find it's launcherIcon in the launcher: - l = Launcher() + charmap_icon = self.launcher.model.get_icon_by_tooltip_text('Character Map') + if charmap_icon: + self.launcher.get_launcher_for_monitor(0).click_launcher_icon(charmap_icon) + else: + self.fail("Could not find launcher icon in launcher.") - launcher_icons = l.get_launcher_icons() - found = False - for icon in launcher_icons: - if icon.tooltip_text == 'Character Map': - found = True - l.click_launcher_icon(icon) - self.assertTrue(found, "Could not find launcher icon in launcher.") - - sleep(1) + sleep(3) for win in self.bamf.get_open_windows(): if win.is_valid: if win.title == 'Character Map': @@ -106,8 +90,8 @@ class ShowDesktopTests(AutopilotTestCase): self.assertTrue(win.is_hidden, "Window '%s' should still be hidden." % (win.title)) # hide desktop - now all windows should be visible: - kb.press_and_release('Control+Alt+d') - sleep(1) + self.keyboard.press_and_release('Control+Alt+d') + sleep(3) for win in self.bamf.get_open_windows(): if win.is_valid: self.assertFalse(win.is_hidden, "Window '%s' is not shown after show desktop deactivated." % (win.title)) @@ -131,10 +115,9 @@ class ShowDesktopTests(AutopilotTestCase): sleep(0.5) self.assertTrue(found, "Could not find 'Show Desktop' entry in switcher.") switcher.stop() - kb = Keyboard() - self.addCleanup(kb.press_and_release, keys='Control+Alt+d') + self.addCleanup(self.keyboard.press_and_release, keys='Control+Alt+d') - sleep(1) + sleep(3) open_wins = self.bamf.get_open_windows() self.assertGreaterEqual(len(open_wins), 2) for win in open_wins: diff --git a/tests/autopilot/autopilot/tests/test_switcher.py b/tests/autopilot/autopilot/tests/test_switcher.py index 1bb30b696..31dca9f6d 100644 --- a/tests/autopilot/autopilot/tests/test_switcher.py +++ b/tests/autopilot/autopilot/tests/test_switcher.py @@ -6,45 +6,29 @@ # under the terms of the GNU General Public License version 3, as published # by the Free Software Foundation. -from compizconfig import Setting -from compizconfig import Plugin from subprocess import call -from testtools.matchers import Equals -from testtools.matchers import NotEquals +from testtools.matchers import Equals, NotEquals, Contains, Not from time import sleep from autopilot.emulators.bamf import Bamf from autopilot.emulators.unity.switcher import Switcher -from autopilot.glibrunner import GlibRunner -from autopilot.globals import global_context from autopilot.tests import AutopilotTestCase class SwitcherTests(AutopilotTestCase): """Test the switcher.""" - run_test_with = GlibRunner def set_timeout_setting(self, value): - self.setting.Value = value - global_context.Write() + self.set_unity_option("alt_tab_timeout", value) def setUp(self): - self.plugin = Plugin(global_context, "unityshell") - self.setting = Setting(self.plugin, "alt_tab_timeout") - self.bamf = Bamf() - - self.bamf.launch_application("gucharmap.desktop") - self.bamf.launch_application("gcalctool.desktop") - self.bamf.launch_application("mahjongg.desktop") - super(SwitcherTests, self).setUp() - self.server = Switcher() + self.start_app('Character Map') + self.start_app('Calculator') + self.start_app('Mahjongg') def tearDown(self): - call(["killall", "gcalctool"]) - call(["killall", "gucharmap"]) - call(["killall", "mahjongg"]) super(SwitcherTests, self).tearDown() sleep(1) @@ -52,34 +36,264 @@ class SwitcherTests(AutopilotTestCase): self.set_timeout_setting(False) sleep(1) - self.server.initiate() + self.switcher.initiate() sleep(.2) - start = self.server.get_selection_index() - self.server.next_icon() + start = self.switcher.get_selection_index() + self.switcher.next_icon() sleep(.2) - end = self.server.get_selection_index() - self.server.terminate() + end = self.switcher.get_selection_index() + self.switcher.terminate() self.assertThat(start, NotEquals(0)) self.assertThat(end, Equals(start + 1)) - self.set_timeout_setting(True) def test_switcher_move_prev(self): self.set_timeout_setting(False) sleep(1) - self.server.initiate() + self.switcher.initiate() sleep(.2) - start = self.server.get_selection_index() - self.server.previous_icon() + start = self.switcher.get_selection_index() + self.switcher.previous_icon() sleep(.2) - end = self.server.get_selection_index() - self.server.terminate() + end = self.switcher.get_selection_index() + self.switcher.terminate() self.assertThat(start, NotEquals(0)) self.assertThat(end, Equals(start - 1)) self.set_timeout_setting(True) + + def test_switcher_arrow_key_does_not_init(self): + self.set_timeout_setting(False) + sleep(1) + + self.switcher.initiate_right_arrow() + sleep(.2) + + self.assertThat(self.switcher.get_is_visible(), Equals(False)) + self.switcher.terminate() + self.set_timeout_setting(True) + + def test_lazy_switcher_initiate(self): + self.set_timeout_setting(False) + sleep(1) + + self.keybinding_hold("switcher/reveal_normal") + self.addCleanup(self.keybinding_release, "switcher/reveal_normal") + sleep(1) + self.assertThat(self.switcher.get_is_visible(), Equals(False)) + + sleep(1) + self.keybinding_tap("switcher/reveal_normal") + self.addCleanup(self.keybinding, "switcher/cancel") + sleep(.5) + self.assertThat(self.switcher.get_is_visible(), Equals(True)) + + def test_switcher_cancel(self): + self.set_timeout_setting(False) + sleep(1) + + self.switcher.initiate() + self.addCleanup(self.switcher.terminate) + sleep(.2) + + self.assertThat(self.switcher.get_is_visible(), Equals(True)) + + self.switcher.cancel() + sleep(.2) + + self.assertThat(self.switcher.get_is_visible(), Equals(False)) + + def test_lazy_switcher_cancel(self): + self.set_timeout_setting(False) + sleep(1) + + self.keybinding_hold("switcher/reveal_normal") + self.addCleanup(self.keybinding_release, "switcher/reveal_normal") + sleep(1) + self.assertThat(self.switcher.get_is_visible(), Equals(False)) + + sleep(1) + self.keybinding_tap("switcher/reveal_normal") + self.addCleanup(self.keybinding, "switcher/cancel") + sleep(.5) + self.assertThat(self.switcher.get_is_visible(), Equals(True)) + + self.switcher.cancel() + sleep(.2) + + self.assertThat(self.switcher.get_is_visible(), Equals(False)) + +class SwitcherDetailsTests(AutopilotTestCase): + """Test the details mode for the switcher.""" + + def test_switcher_starts_in_normal_mode(self): + """Switcher must start in normal (i.e.- not details) mode.""" + self.start_app("Character Map") + sleep(1) + + self.switcher.initiate() + self.addCleanup(self.switcher.terminate) + self.assertThat(self.switcher.get_is_in_details_mode(), Equals(False)) + + def test_details_mode_on_delay(self): + self.close_all_app('Character Map') + self.workspace.switch_to(1) + self.start_app("Character Map") + sleep(1) + self.start_app("Character Map") + sleep(1) + + # Need to start a different app, so it has focus, so alt-tab goes to + # the character map. + self.start_app('Mahjongg') + sleep(1) + + self.switcher.initiate() + self.addCleanup(self.switcher.terminate) + # Wait longer than details mode. + sleep(3) + self.assertTrue(self.switcher.get_is_in_details_mode()) + + def test_no_details_for_apps_on_different_workspace(self): + # Re bug: 933406 + self.close_all_app('Character Map') + + self.workspace.switch_to(1) + self.start_app("Character Map") + sleep(1) + self.workspace.switch_to(2) + self.start_app("Character Map") + sleep(1) + # Need to start a different app, so it has focus, so alt-tab goes to + # the character map. + self.start_app('Mahjongg') + sleep(1) + + self.switcher.initiate() + self.addCleanup(self.switcher.terminate) + # Wait longer than details mode. + sleep(3) + self.assertFalse(self.switcher.get_is_in_details_mode()) + + +class SwitcherDetailsModeTests(AutopilotTestCase): + """Tests for the details mode of the switcher. + + Tests for initiation with both grave (`) and Down arrow. + + """ + + scenarios = [ + ('initiate_with_grave', {'initiate_keycode': '`'}), + ('initiate_with_down', {'initiate_keycode': 'Down'}), + ] + + def test_can_start_details_mode(self): + """Must be able to initiate details mode using selected scenario keycode.""" + self.start_app("Character Map") + self.switcher.initiate() + self.addCleanup(self.switcher.terminate) + self.keyboard.press_and_release(self.initiate_keycode) + self.assertThat(self.switcher.get_is_in_details_mode(), Equals(True)) + + def test_tab_from_last_detail_works(self): + """Pressing tab while showing last switcher item in details mode + must select first item in the model in non-details mode. + + """ + self.start_app("Character Map") + self.switcher.initiate() + self.addCleanup(self.switcher.terminate) + while self.switcher.get_selection_index() < self.switcher.get_model_size() -1: + self.switcher.next_icon() + self.keyboard.press_and_release(self.initiate_keycode) + sleep(0.5) + self.switcher.next_icon() + self.assertThat(self.switcher.get_selection_index(), Equals(0)) + + +class SwitcherWorkspaceTests(AutopilotTestCase): + """Test Switcher behavior with respect to multiple workspaces.""" + + def test_switcher_shows_current_workspace_only(self): + """Switcher must show apps from the current workspace only.""" + self.close_all_app('Calculator') + self.close_all_app('Character Map') + + self.workspace.switch_to(1) + self.start_app("Calculator") + sleep(1) + self.workspace.switch_to(2) + self.start_app("Character Map") + sleep(1) + + self.switcher.initiate() + sleep(1) + icon_names = [i.tooltip_text for i in self.switcher.get_switcher_icons()] + self.switcher.terminate() + self.assertThat(icon_names, Contains("Character Map")) + self.assertThat(icon_names, Not(Contains("Calculator"))) + + def test_switcher_all_mode_shows_all_apps(self): + """Test switcher 'show_all' mode shows apps from all workspaces.""" + self.close_all_app('Calculator') + self.close_all_app('Character Map') + + self.workspace.switch_to(1) + self.start_app("Calculator") + sleep(1) + self.workspace.switch_to(2) + self.start_app("Character Map") + sleep(1) + + self.switcher.initiate_all_mode() + sleep(1) + icon_names = [i.tooltip_text for i in self.switcher.get_switcher_icons()] + self.switcher.terminate() + self.assertThat(icon_names, Contains("Character Map")) + self.assertThat(icon_names, Contains("Calculator")) + + def test_switcher_can_switch_to_minimised_window(self): + """Switcher must be able to switch to a minimised window when there's + another instance of the same application on a different workspace.""" + # disable automatic gridding of the switcher after a timeout, since it makes + # it harder to write the tests. + self.set_unity_option("alt_tab_timeout", False) + self.close_all_app("Calculator") + self.close_all_app("Mahjongg") + + self.workspace.switch_to(1) + self.start_app("Mahjongg") + + self.workspace.switch_to(3) + self.start_app("Mahjongg") + sleep(1) + self.keybinding("window/minimize") + sleep(1) + + self.start_app("Calculator") + sleep(1) + + self.switcher.initiate() + sleep(1) + while self.switcher.current_icon.tooltip_text != 'Mahjongg': + self.switcher.next_icon() + sleep(1) + self.switcher.stop() + sleep(1) + + #get calculator windows - there should be only one: + mahjongg_apps = self.get_app_instances("Mahjongg") + self.assertThat(len(mahjongg_apps), Equals(1)) + wins = mahjongg_apps[0].get_windows() + self.assertThat(len(wins), Equals(2)) + # Ideally we should be able to find the instance that is on the + # current workspace and ask that one if it is hidden. + self.assertFalse(wins[0].is_hidden) + self.assertFalse(wins[1].is_hidden) + diff --git a/tests/autopilot/autopilot/utilities.py b/tests/autopilot/autopilot/utilities.py index 42f93e4f3..489674f36 100644 --- a/tests/autopilot/autopilot/utilities.py +++ b/tests/autopilot/autopilot/utilities.py @@ -12,6 +12,7 @@ """Various utility classes and functions that are useful when running tests.""" + from Xlib import X, display, protocol _display = display.Display() @@ -34,6 +35,16 @@ def make_window_skip_taskbar(window, set_flag=True): _display.sync() +def get_desktop_viewport(): + """Get the x,y coordinates for the current desktop viewport top-left corner.""" + return _getProperty('_NET_DESKTOP_VIEWPORT') + + +def get_desktop_geometry(): + """Get the full width and height of the desktop, including all the viewports.""" + return _getProperty('_NET_DESKTOP_GEOMETRY') + + def _setProperty(_type, data, win=None, mask=None): """ Send a ClientMessage event to a window""" if not win: @@ -52,3 +63,10 @@ def _setProperty(_type, data, win=None, mask=None): if not mask: mask = (X.SubstructureRedirectMask | X.SubstructureNotifyMask) _display.screen().root.send_event(ev, event_mask=mask) + + +def _getProperty(_type, win=None): + if not win: + win = _display.screen().root + atom = win.get_full_property(_display.get_atom(_type), X.AnyPropertyType) + if atom: return atom.value diff --git a/tests/autopilot/bin/run_autopilot b/tests/autopilot/bin/run_autopilot deleted file mode 100755 index 030692438..000000000 --- a/tests/autopilot/bin/run_autopilot +++ /dev/null @@ -1,46 +0,0 @@ -#!/usr/bin/env python - -import os -import sys -import junitxml -from argparse import ArgumentParser -from unittest.loader import TestLoader -from unittest.runner import TextTestRunner - - -if __name__ == "__main__": - parser = ArgumentParser() - parser.add_argument('-o', "--output", required=False, - help='Write test result report to file. Defaults to stdout') - parser.add_argument('-f', "--format", choices=['text', 'xml'], default='text', - required=False, help='Specify desired output format. Default is "text".') - args = parser.parse_args() - - if args.output == None: - results_stream = sys.stdout - else: - try: - path = os.path.dirname(args.output) - if path != '' and not os.path.exists(path): - os.makedirs(path) - results_stream = open(args.output, 'w') - except: - results_stream = sys.stdout - - loader = TestLoader() - test_suite = loader.discover('autopilot.tests') - - if args.format == "xml": - result = junitxml.JUnitXmlResult(results_stream) - result.startTestRun() - test_suite.run(result) - result.stopTestRun() - results_stream.close() - if not result.wasSuccessful: - exit(1) - elif args.format == "text": - runner = TextTestRunner(stream=results_stream) - success = runner.run(test_suite).wasSuccessful() - if not success: - exit(1) - diff --git a/tests/autopilot/setup.py b/tests/autopilot/setup.py index 941ff662d..03791bdb8 100644 --- a/tests/autopilot/setup.py +++ b/tests/autopilot/setup.py @@ -12,5 +12,4 @@ setup( url='https://launchpad.net/unity', license='GPLv3', packages=find_packages(), - scripts=['bin/run_autopilot'], ) diff --git a/tests/autopilot/tests/README b/tests/autopilot/tests/README new file mode 100644 index 000000000..7604582ec --- /dev/null +++ b/tests/autopilot/tests/README @@ -0,0 +1,3 @@ +This folder contains tests for autopilot itself. These are not "autopilot +tests". Unless you want to hack autopilot itself, you are probably in the +wrong place. Try looking in ../autopilot/tests/ instead. diff --git a/tests/autopilot/tests/__init__.py b/tests/autopilot/tests/__init__.py new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/tests/autopilot/tests/__init__.py diff --git a/tests/autopilot/tests/test_compiz_key_translate.py b/tests/autopilot/tests/test_compiz_key_translate.py new file mode 100644 index 000000000..a0bed1b0b --- /dev/null +++ b/tests/autopilot/tests/test_compiz_key_translate.py @@ -0,0 +1,67 @@ +# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*- +# Copyright 2012 Canonical +# Author: Thomi Richards +# +# This program is free software: you can redistribute it and/or modify it +# under the terms of the GNU General Public License version 3, as published +# by the Free Software Foundation. + +from testscenarios import TestWithScenarios +from testtools import TestCase +from testtools.matchers import raises, Equals + +from autopilot.keybindings import _translate_compiz_keystroke_string as translate_func + +class KeyTranslateArgumentTests(TestWithScenarios, TestCase): + """Tests that the compizconfig keycode translation routes work as advertised.""" + + scenarios = [ + ('bool', {'input': True}), + ('int', {'input': 42}), + ('float', {'input': 0.321}), + ('none', {'input': None}), + ] + + def test_requires_string_instance(self): + """Function must raise TypeError unless given an instance of basestring.""" + self.assertThat(lambda: translate_func(self.input), raises(TypeError)) + + +class TranslationTests(TestWithScenarios, TestCase): + """Test that we get the result we expect, with the given input.""" + + scenarios = [ + ('empty string', dict(input='', expected='')), + ('single simpe letter', dict(input='a', expected='a')), + ('trailing space', dict(input='d ', expected='d')), + ('only whitespace', dict(input='\t\n ', expected='')), + ('special key: Ctrl', dict(input='<Control>', expected='Ctrl')), + ('special key: Primary', dict(input='<Primary>', expected='Ctrl')), + ('special key: Alt', dict(input='<Alt>', expected='Alt')), + ('special key: Shift', dict(input='<Shift>', expected='Shift')), + ('direction key up', dict(input='Up', expected='Up')), + ('direction key down', dict(input='Down', expected='Down')), + ('direction key left', dict(input='Left', expected='Left')), + ('direction key right', dict(input='Right', expected='Right')), + ('Ctrl+a', dict(input='<Control>a', expected='Ctrl+a')), + ('Primary+a', dict(input='<Control>a', expected='Ctrl+a')), + ('Shift+s', dict(input='<Shift>s', expected='Shift+s')), + ('Alt+d', dict(input='<Alt>d', expected='Alt+d')), + ('Super+w', dict(input='<Super>w', expected='Super+w')), + ('Ctrl+Up', dict(input='<Control>Up', expected='Ctrl+Up')), + ('Primary+Down', dict(input='<Control>Down', expected='Ctrl+Down')), + ('Alt+Left', dict(input='<Alt>Left', expected='Alt+Left')), + ('Shift+F3', dict(input='<Shift>F3', expected='Shift+F3')), + ('duplicate keys Ctrl+Ctrl', dict(input='<Control><Control>', expected='Ctrl')), + ('duplicate keys Ctrl+Primary', dict(input='<Control><Primary>', expected='Ctrl')), + ('duplicate keys Ctrl+Primary', dict(input='<Primary><Control>', expected='Ctrl')), + ('duplicate keys Alt+Alt', dict(input='<Alt><Alt>', expected='Alt')), + ('duplicate keys Ctrl+Primary+left', dict(input='<Control><Primary>Left', expected='Ctrl+Left')), + ('first key wins', dict(input='<Control><Alt>Down<Alt>', expected='Ctrl+Alt+Down')), + ('Getting silly now', dict(input='<Control><Primary><Shift><Shift><Alt>Left', expected='Ctrl+Shift+Alt+Left')), + ] + + def test_translation(self): + self.assertThat(translate_func(self.input), Equals(self.expected)) + + diff --git a/tests/data/lenses/files/files.lens b/tests/data/lenses/files/files.lens index 5f5358cee..3b33ebb03 100644 --- a/tests/data/lenses/files/files.lens +++ b/tests/data/lenses/files/files.lens @@ -5,5 +5,5 @@ Name=Files Icon=/usr/share/unity-lens-files/files.png Description=Search for Files & Folders SearchHint=Search Files & Folders -Visible=true +Visible=false Shortcut=f diff --git a/tests/test_filesystem_lenses.cpp b/tests/test_filesystem_lenses.cpp index b3a24bdc8..48447ec7d 100644 --- a/tests/test_filesystem_lenses.cpp +++ b/tests/test_filesystem_lenses.cpp @@ -45,18 +45,21 @@ void WaitForLensesToLoad(FilesystemLenses& lenses) TEST(TestFilesystemLenses, TestConstruction) { FilesystemLenses lenses0; - FilesystemLenses lenses1(TESTDATADIR"/lenses"); + LensDirectoryReader::Ptr test_reader(new LensDirectoryReader(TESTDATADIR"/lenses")); + FilesystemLenses lenses1(test_reader); } TEST(TestFilesystemLenses, TestFileLoading) { - FilesystemLenses lenses(TESTDATADIR"/lenses"); + LensDirectoryReader::Ptr test_reader(new LensDirectoryReader(TESTDATADIR"/lenses")); + FilesystemLenses lenses(test_reader); WaitForLensesToLoad(lenses); } TEST(TestFilesystemLenses, TestLensesAdded) { - FilesystemLenses lenses(TESTDATADIR"/lenses"); + LensDirectoryReader::Ptr test_reader(new LensDirectoryReader(TESTDATADIR"/lenses")); + FilesystemLenses lenses(test_reader); unsigned int n_lenses = 0; auto lens_added_cb = [&n_lenses](Lens::Ptr & p) @@ -72,7 +75,8 @@ TEST(TestFilesystemLenses, TestLensesAdded) TEST(TestFilesystemLenses, TestLensContent) { - FilesystemLenses lenses(TESTDATADIR"/lenses"); + LensDirectoryReader::Ptr test_reader(new LensDirectoryReader(TESTDATADIR"/lenses")); + FilesystemLenses lenses(test_reader); WaitForLensesToLoad(lenses); // Test that the lenses have loaded correctly @@ -95,7 +99,7 @@ TEST(TestFilesystemLenses, TestLensContent) EXPECT_EQ(lens->icon_hint, "/usr/share/unity-lens-files/files.png"); EXPECT_EQ(lens->description, "Search for Files & Folders"); EXPECT_EQ(lens->search_hint, "Search Files & Folders"); - EXPECT_EQ(lens->visible, true); + EXPECT_EQ(lens->visible, false); EXPECT_EQ(lens->shortcut, "f"); lens = lenses.GetLens("social.lens"); @@ -106,7 +110,7 @@ TEST(TestFilesystemLenses, TestLensContent) EXPECT_EQ(lens->icon_hint, "/usr/share/unity-lens-social/social.png"); EXPECT_EQ(lens->description, ""); EXPECT_EQ(lens->search_hint, ""); - EXPECT_EQ(lens->visible, false); + EXPECT_EQ(lens->visible, true); EXPECT_EQ(lens->shortcut, ""); } diff --git a/tests/test_gdbus_proxy.cpp b/tests/test_gdbus_proxy.cpp new file mode 100644 index 000000000..704cf690f --- /dev/null +++ b/tests/test_gdbus_proxy.cpp @@ -0,0 +1,136 @@ +#include <gtest/gtest.h> +#include <glib-object.h> +#include <UnityCore/GLibWrapper.h> +#include <UnityCore/GLibDBusProxy.h> + +using namespace std; +using namespace unity; + +namespace +{ + +GMainLoop* loop_ = NULL; +glib::DBusProxy* proxy = NULL; + +class TestGDBusProxy: public ::testing::Test +{ +public: + TestGDBusProxy() + : connected_result(false) + , got_signal_return(false) + , got_result_return(false) + { + } + bool connected_result; + bool got_signal_return; + bool got_result_return; +}; + +TEST_F(TestGDBusProxy, TestConstruction) +{ + loop_ = g_main_loop_new(NULL, FALSE); + proxy = new glib::DBusProxy("com.canonical.Unity.Test", + "/com/canonical/gdbus_wrapper", + "com.canonical.gdbus_wrapper"); + // performs a check on the proxy, if the proxy is connected, report a sucess + auto timeout_check = [] (gpointer data) -> gboolean + { + TestGDBusProxy* self = static_cast<TestGDBusProxy*>(data); + if (proxy->IsConnected()) + { + self->connected_result = true; + g_main_loop_quit(loop_); + return FALSE; + } + else + { + self->connected_result = false; + return TRUE; + } + }; + + + // if the proxy is not connected when this lambda runs, fail. + auto timeout_bailout = [] (gpointer data) -> gboolean + { + TestGDBusProxy* self = static_cast<TestGDBusProxy*>(data); + // reached timeout, failed testing + self->connected_result = false; + g_main_loop_quit(loop_); + return FALSE; + }; + + guint timeout_source = g_timeout_add_seconds(1, timeout_check, this); // check once a second + guint bailout_source = g_timeout_add_seconds(10, timeout_bailout, this); // bail out after ten + + g_main_loop_run(loop_); + g_source_remove(timeout_source); + g_source_remove(bailout_source); + + EXPECT_EQ(connected_result, true); +} + +TEST_F(TestGDBusProxy, TestMethodReturn) +{ + // Our service is setup so that if you call the TestMethod method, it will emit the TestSignal method + // with whatever string you pass in + gchar* expected_return = (gchar *)"TestStringTestString☻☻☻"; // cast to get gcc to shut up + gchar* returned_result = g_strdup("Not equal"); + gchar* returned_signal = g_strdup("Not equal"); + + GVariant* param_value = g_variant_new_string(expected_return); + GVariant* parameters = g_variant_new_tuple(¶m_value, 1); + // signal callback + auto signal_connection = [&](GVariant *variant) + { + if (variant != nullptr) + { + g_free(returned_signal); + returned_signal = g_strdup(g_variant_get_string(g_variant_get_child_value(variant, 0), NULL)); + } + + got_signal_return = true; + if (got_signal_return && got_result_return) + g_main_loop_quit(loop_); + }; + + // method callback + auto method_connection = [&](GVariant *variant) + { + if (variant != nullptr) + { + g_free(returned_result); + returned_result = g_strdup(g_variant_get_string(g_variant_get_child_value(variant, 0), NULL)); + } + + got_result_return = true; + if (got_signal_return && got_result_return) + g_main_loop_quit(loop_); + }; + + auto timeout_bailout = [] (gpointer data) -> gboolean // bail out after 10 seconds + { + g_main_loop_quit(loop_); + return FALSE; + }; + + guint bailout_source = g_timeout_add_seconds(10, timeout_bailout, this); + + EXPECT_EQ(proxy->IsConnected(), true); // fail if we are not connected + proxy->Connect("TestSignal", signal_connection); + proxy->Call("TestMethod", parameters, method_connection); + + + // next check we get 30 entries from this specific known callback + g_main_loop_run(loop_); + + EXPECT_EQ(g_strcmp0(expected_return, returned_result), 0); + EXPECT_EQ(g_strcmp0(expected_return, returned_signal), 0); + + g_free(returned_result); + g_free(returned_signal); + g_source_remove(bailout_source); +} + + +} diff --git a/tests/test_service_gdbus_wrapper.c b/tests/test_service_gdbus_wrapper.c new file mode 100644 index 000000000..52081037f --- /dev/null +++ b/tests/test_service_gdbus_wrapper.c @@ -0,0 +1,201 @@ +#include "test_service_gdbus_wrapper.h" +#include <unity.h> +#include <gio/gio.h> + +const char * gdbus_wrapper_interface = +"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +"<node name=\"/\">\n" +" <interface name=\"com.canonical.gdbus_wrapper\">\n" +"<!-- Properties -->\n" +" <!-- None -->\n" +"\n" +"<!-- Functions -->\n" +" <method name=\"TestMethod\">\n" +" <!-- in -->\n" +" <arg type=\"s\" name=\"query\" direction=\"in\" />\n" +" <!-- out -->\n" +" <arg type=\"s\" name=\"target\" direction=\"out\" />\n" +" </method>\n" +"\n" +"<!-- Signals -->\n" +" <signal name=\"TestSignal\">\n" +" <arg type=\"s\" name=\"target\" direction=\"out\" />\n" +" </signal>\n" +"\n" +"<!-- End of interesting stuff -->\n" +"\n" +" </interface>\n" +"</node>\n" +; +static void bus_got_cb (GObject *object, GAsyncResult * res, gpointer user_data); +static void bus_method (GDBusConnection *connection, + const gchar *sender, + const gchar *object_path, + const gchar *interface_name, + const gchar *method_name, + GVariant *parameters, + GDBusMethodInvocation *invocation, + gpointer user_data); + +G_DEFINE_TYPE(ServiceGDBusWrapper, service_gdbus_wrapper, G_TYPE_OBJECT); +static GDBusNodeInfo * node_info = NULL; +static GDBusInterfaceInfo * iface_info = NULL; +static GDBusInterfaceVTable bus_vtable = { + method_call: bus_method, + get_property: NULL, + set_property: NULL, +}; + + +struct _ServiceGDBusWrapperPrivate +{ + GDBusConnection * bus; + GCancellable * bus_lookup; + guint bus_registration; +}; + +static void +service_gdbus_wrapper_dispose(GObject* object) +{ + ServiceGDBusWrapper* self = SERVICE_GDBUS_WRAPPER(object); + if (self->priv->bus_lookup != NULL) { + g_cancellable_cancel(self->priv->bus_lookup); + g_object_unref(self->priv->bus_lookup); + self->priv->bus_lookup = NULL; + } + + if (self->priv->bus_registration != 0) { + g_dbus_connection_unregister_object(self->priv->bus, self->priv->bus_registration); + self->priv->bus_registration = 0; + } + + if (self->priv->bus != NULL) { + g_object_unref(self->priv->bus); + self->priv->bus = NULL; + } + +} + +static void +service_gdbus_wrapper_class_init(ServiceGDBusWrapperClass* klass) +{ + G_OBJECT_CLASS(klass)->dispose = service_gdbus_wrapper_dispose; + g_type_class_add_private (klass, sizeof (ServiceGDBusWrapperPrivate)); + + if (node_info == NULL) + { + GError * error = NULL; + + node_info = g_dbus_node_info_new_for_xml(gdbus_wrapper_interface, &error); + if (error != NULL) + { + g_error("Unable to parse GDBUS_WRAPPER interface: %s", error->message); + g_error_free(error); + } + } + + if (node_info != NULL && iface_info == NULL) + { + iface_info = g_dbus_node_info_lookup_interface(node_info,"com.canonical.gdbus_wrapper"); + if (iface_info == NULL) + { + g_error("Unable to find interface 'com.canonical.gdbus_wrapper'"); + } + } + +} + +static void +service_gdbus_wrapper_init(ServiceGDBusWrapper* self) +{ + self->priv = G_TYPE_INSTANCE_GET_PRIVATE(self, SERVICE_TYPE_GDBUS_WRAPPER, ServiceGDBusWrapperPrivate); + self->priv->bus = NULL; + self->priv->bus_lookup = NULL; + self->priv->bus_registration = 0; + + self->priv->bus_lookup = g_cancellable_new(); + g_bus_get(G_BUS_TYPE_SESSION, self->priv->bus_lookup, bus_got_cb, self); + +} + +ServiceGDBusWrapper* +service_gdbus_wrapper_new() +{ + return g_object_new(SERVICE_TYPE_GDBUS_WRAPPER, NULL); +} + +static void +bus_got_cb (GObject *object, GAsyncResult * res, gpointer user_data) +{ + GError * error = NULL; + ServiceGDBusWrapper * self = SERVICE_GDBUS_WRAPPER(user_data); + GDBusConnection * bus; + + bus = g_bus_get_finish(res, &error); + if (error != NULL) { + g_critical("Unable to get bus: %s", error->message); + g_error_free(error); + return; + } + + self->priv->bus = bus; + + /* Register object */ + self->priv->bus_registration = g_dbus_connection_register_object(bus, + /* path */ "/com/canonical/gdbus_wrapper", + /* interface */ iface_info, + /* vtable */ &bus_vtable, + /* userdata */ self, + /* destroy */ NULL, + /* error */ &error); + + if (error != NULL) { + g_critical ("Unable to create bus connection object, %s", error->message); + g_error_free(error); + return; + } + + return; +} + +static void +bus_method (GDBusConnection *connection, const gchar *sender, const gchar *object_path, const gchar *interface_name, const gchar *method_name, GVariant *parameters, GDBusMethodInvocation *invocation, gpointer user_data) +{ + if (g_strcmp0(method_name, "TestMethod") == 0) + { + GVariant * ret = NULL; + GVariant * sig_data = NULL; + GVariant * tmp = NULL; + gchar * query = NULL; + GError * error = NULL; + g_variant_get(parameters, "(s)", &query); + + tmp = g_variant_new_string(query); + ret = g_variant_new_tuple(&tmp, 1); + g_dbus_method_invocation_return_value(invocation, ret); + tmp = NULL; + + // emit a signal with the same string as passed in every time this method is called + + tmp = g_variant_new_string(query); + sig_data = g_variant_new_tuple(&tmp, 1); + g_dbus_connection_emit_signal(connection, + NULL, /* destination bus, we don't care */ + "/com/canonical/gdbus_wrapper", /* object path */ + "com.canonical.gdbus_wrapper", /* interface name */ + "TestSignal", /* Signal name */ + sig_data, /* parameter */ + &error); /* error */ + + if (error != NULL) + { + g_critical ("could not emit signal TestSignal with data %s", query); + g_error_free(error); + } + + g_free(query); + } + + return; +} + diff --git a/tests/test_service_gdbus_wrapper.h b/tests/test_service_gdbus_wrapper.h new file mode 100644 index 000000000..6344e7569 --- /dev/null +++ b/tests/test_service_gdbus_wrapper.h @@ -0,0 +1,46 @@ +#ifndef _SERVICE_GDBUS_WRAPPER_H_ +#define _SERVICE_GDBUS_WRAPPER_H_ + +#include <glib-object.h> +G_BEGIN_DECLS + +#define SERVICE_TYPE_GDBUS_WRAPPER (service_gdbus_wrapper_get_type ()) + +#define SERVICE_GDBUS_WRAPPER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj),\ + SERVICE_TYPE_GDBUS_WRAPPER, ServiceGDBusWrapper)) + +#define SERVICE_GDBUS_WRAPPER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass),\ + SERVICE_TYPE_GDBUS_WRAPPER, ServiceGDBusWrapperClass)) + +#define SERVICE_IS_GDBUS_WRAPPER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj),\ + SERVICE_TYPE_GDBUS_WRAPPER)) + +#define SERVICE_IS_GDBUS_WRAPPER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass),\ + SERVICE_TYPE_GDBUS_WRAPPER)) + +#define ServiceGDBusWrapper_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj),\ + SERVICE_TYPE_GDBUS_WRAPPER, ServiceGDBusWrapperClass)) + +typedef struct _ServiceGDBusWrapper ServiceGDBusWrapper; +typedef struct _ServiceGDBusWrapperClass ServiceGDBusWrapperClass; +typedef struct _ServiceGDBusWrapperPrivate ServiceGDBusWrapperPrivate; + +struct _ServiceGDBusWrapper +{ + GObject parent; + + ServiceGDBusWrapperPrivate *priv; +}; + +struct _ServiceGDBusWrapperClass +{ + GObjectClass parent_class; +}; + +GType service_gdbus_wrapper_get_type(void) G_GNUC_CONST; + +ServiceGDBusWrapper* service_gdbus_wrapper_new(void); + +G_END_DECLS + +#endif /* _SERVICE_GDBUS_WRAPPER_H_ */ diff --git a/tests/test_service_main.c b/tests/test_service_main.c index e03bef41c..921027e63 100644 --- a/tests/test_service_main.c +++ b/tests/test_service_main.c @@ -3,6 +3,7 @@ #include "test_service_lens.h" #include "test_service_model.h" #include "test_service_hud.h" +#include "test_service_gdbus_wrapper.h" static void on_bus_aquired(GDBusConnection* conn, const gchar* name, gpointer null); static void handle_method_call(GDBusConnection *connection, @@ -36,7 +37,7 @@ static GMainLoop* loop_ = NULL; static ServiceLens* lens_ = NULL; static ServiceModel* model_ = NULL; static ServiceHud* hud_ = NULL; - +static ServiceGDBusWrapper* gdbus_wrapper_ = NULL; gint main(gint argc, gchar** argv) { @@ -47,6 +48,7 @@ main(gint argc, gchar** argv) lens_ = service_lens_new(); model_ = service_model_new(); hud_ = service_hud_new(); + gdbus_wrapper_ = service_gdbus_wrapper_new(); g_bus_own_name(G_BUS_TYPE_SESSION, "com.canonical.Unity.Test", diff --git a/tests/test_shortcut_model.cpp b/tests/test_shortcut_model.cpp index 1e0d521ba..197ad8edf 100644 --- a/tests/test_shortcut_model.cpp +++ b/tests/test_shortcut_model.cpp @@ -33,14 +33,14 @@ TEST(TestShortcutModel, TestConstruction) hints.push_back(new MockHint("Launcher", "", "", "Description 1", COMPIZ_KEY_OPTION, "Plugin 1", "key_option_1")); hints.push_back(new MockHint("Launcher", "", "", "Description 2", HARDCODED_OPTION, "Value 2")); hints.push_back(new MockHint("Dash", "Prefix", "Postfix", "Description 3", COMPIZ_KEY_OPTION, "Plugin 3", "key_option_3")); - hints.push_back(new MockHint("Top Bar", "Prefix", "Postfix", "Description 4", HARDCODED_OPTION, "Value4")); + hints.push_back(new MockHint("Menu Bar", "Prefix", "Postfix", "Description 4", HARDCODED_OPTION, "Value4")); Model model(hints); EXPECT_EQ(model.categories().size(), 3); EXPECT_EQ(model.hints()["Launcher"].size(), 2); EXPECT_EQ(model.hints()["Dash"].size(), 1); - EXPECT_EQ(model.hints()["Top Bar"].size(), 1); + EXPECT_EQ(model.hints()["Menu Bar"].size(), 1); EXPECT_EQ(model.hints()["Unity"].size(), 0); } @@ -51,7 +51,7 @@ TEST(TestShortcutModel, TestFill) hints.push_back(new MockHint("Launcher", "", "", "Description 1", COMPIZ_KEY_OPTION, "Plugin 1", "key_option_1")); hints.push_back(new MockHint("Launcher", "", "", "Description 2", HARDCODED_OPTION, "Value 2")); hints.push_back(new MockHint("Dash", "Prefix", "Postfix", "Description 3", COMPIZ_KEY_OPTION, "Plugin 3", "key_option_3")); - hints.push_back(new MockHint("Top Bar", "Prefix", "Postfix", "Description 4", HARDCODED_OPTION, "Value 4")); + hints.push_back(new MockHint("Menu Bar", "Prefix", "Postfix", "Description 4", HARDCODED_OPTION, "Value 4")); Model model(hints); @@ -61,7 +61,7 @@ TEST(TestShortcutModel, TestFill) EXPECT_EQ(model.hints()["Launcher"].front()->value(), "Plugin 1-key_option_1"); EXPECT_EQ(model.hints()["Launcher"].back()->value(), "Value 2"); EXPECT_EQ(model.hints()["Dash"].front()->value(),"Plugin 3-key_option_3"); - EXPECT_EQ(model.hints()["Top Bar"].front()->value(), "Value 4"); + EXPECT_EQ(model.hints()["Menu Bar"].front()->value(), "Value 4"); } TEST(TestShortcutModel, TestProperty) diff --git a/tools/autopilot b/tools/autopilot new file mode 100755 index 000000000..e85120940 --- /dev/null +++ b/tools/autopilot @@ -0,0 +1,162 @@ +#!/usr/bin/env python + +from imp import find_module +import os +import os.path +import sys +from textwrap import dedent +from argparse import ArgumentParser +from unittest.loader import TestLoader +from unittest.runner import TextTestRunner + + +# list autopilot depends here, with the form: +# ('python module name', 'ubuntu package name'), +DEPENDS = [ + ('compizconfig', 'python-compizconfig'), + ('dbus', 'python-dbus'), + ('gobject', 'python-gobject'), + ('gtk', 'python-gtk2'), + ('ibus', 'python-ibus'), + ('junitxml', 'python-junitxml'), + ('testscenarios', 'python-testscenarios'), + ('testtools', 'python-testtools'), + ('xdg', 'python-xdg'), + ('Xlib', 'python-xlib'), +] + + +def check_depends(): + """Check for required dependancies, and print a helpful message if any are + missing. + + If all required modules are present, return True, False otherwise. + """ + missing = [] + for module_name, package_name in DEPENDS: + try: + find_module(module_name) + except ImportError: + missing.append(package_name) + if missing: + print dedent("""\ + You are missing one or more packages required to run autopilot. They + are: + + %s + + Please install these packages and re-run this script. + """ % (' '.join(missing)) + ) + return False + return True + + +def ensure_autopilot_is_importable(): + """Patch sys.path with the local autopilot directory if it's not already + importable. + """ + try: + find_module("autopilot") + except ImportError: + ap_dir = os.path.join(os.path.dirname(__file__), + "../tests/autopilot") + ap_dir = os.path.realpath(ap_dir) + sys.path.append(ap_dir) + print "Patching sys.path to include local autopilot folder '%s'\n" % ap_dir + + +def parse_arguments(): + """Parse command-line arguments, and return an argparse arguments object.""" + parser = ArgumentParser(description="Autopilot test-runner") + subparsers = parser.add_subparsers(help='Run modes', dest="mode") + + parser_run = subparsers.add_parser('run', help="Run autopilot tests") + parser_run.add_argument('-o', "--output", required=False, + help='Write test result report to file. Defaults to stdout') + parser_run.add_argument('-f', "--format", choices=['text', 'xml'], default='text', + required=False, help='Specify desired output format. Default is "text".') + parser_run.add_argument('-r', '--record', action='store_true', default=False, + required=False, help="Record failing tests. Required 'recordmydesktop' app to be installed. Videos \ + are stored in /tmp/autopilot.") + parser_run.add_argument("-rd", "--record-directory", required=False, + default="/tmp/autopilot", type=str, help="Directory to put recorded tests (only if -r) specified.") + parser_run.add_argument("test", nargs="*", help="Specify tests to run, as listed by the 'list' command") + + + parser_list = subparsers.add_parser('list', help="List autopilot tests") + args = parser.parse_args() + + return args + + +def list_tests(): + """Print a list of tests we find inside autopilot.tests.""" + num_tests = 0 + from testtools import iterate_tests + loader = TestLoader() + test_suite = loader.discover('autopilot.tests') + print "Listing all autopilot tests:" + print + for test in iterate_tests(test_suite): + has_scenarios = hasattr(test, "scenarios") + if has_scenarios: + num_tests += len(test.scenarios) + print " *%d %s" % (len(test.scenarios), test.id()) + else: + num_tests += 1 + print test.id() + print "\n %d total tests." % (num_tests) + + +def run_tests(args): + """Run tests, using input from `args`.""" + import junitxml + import autopilot.globals + + if args.record: + autopilot.globals.video_recording_enabled = True + autopilot.globals.video_record_directory = args.record_directory + + loader = TestLoader() + if args.test: + test_suite = loader.loadTestsFromNames(args.test) + else: + test_suite = loader.discover('autopilot.tests') + + if args.output == None: + results_stream = sys.stdout + else: + try: + path = os.path.dirname(args.output) + if path != '' and not os.path.exists(path): + os.makedirs(path) + results_stream = open(args.output, 'w') + except: + results_stream = sys.stdout + if args.format == "xml": + result = junitxml.JUnitXmlResult(results_stream) + result.startTestRun() + test_suite.run(result) + result.stopTestRun() + results_stream.close() + if not result.wasSuccessful: + exit(1) + elif args.format == "text": + runner = TextTestRunner(stream=results_stream) + success = runner.run(test_suite).wasSuccessful() + if not success: + exit(1) + +def main(): + args = parse_arguments() + if args.mode == 'list': + list_tests() + elif args.mode == 'run': + run_tests(args) + +if __name__ == "__main__": + if not check_depends(): + exit(1) + ensure_autopilot_is_importable() + main() diff --git a/tools/unity-introspection-visualiser.py b/tools/unity-introspection-visualiser.py index bdefe0b51..27767a9b1 100755 --- a/tools/unity-introspection-visualiser.py +++ b/tools/unity-introspection-visualiser.py @@ -8,8 +8,8 @@ from os.path import splitext import dbus try: - from autopilot.emulators.unity import Unity -except ImportError: + from autopilot.emulators.unity import get_state_by_path +except ImportError, e: print "Error: could not import the autopilot python module." print "Make sure the autopilot module is in your $PYTHONPATH." exit(1) @@ -22,14 +22,14 @@ except ImportError: exit(1) NEXT_NODE_ID=1 - +NODE_BLACKLIST=["Result"] def string_rep(dbus_type): """Get a string representation of various dbus types.""" if type(dbus_type) == dbus.Boolean: return repr(bool(dbus_type)) if type(dbus_type) == dbus.String: - return str(dbus_type) + return dbus_type.encode('ascii', errors='ignore') if type(dbus_type) in (dbus.Int16, dbus.UInt16, dbus.Int32, dbus.UInt32, dbus.Int64, dbus.UInt64): return repr(int(dbus_type)) if type(dbus_type) == dbus.Double: @@ -42,7 +42,7 @@ def string_rep(dbus_type): def escape(s): """Escape a string so it can be use in a dot label.""" - return pydot.quote_if_necessary(s).replace('<','\\<').replace('>', '\\>') + return pydot.quote_if_necessary(s).replace('<','\\<').replace('>', '\\>').replace("'", "\\'") def traverse_tree(state, parent, graph): @@ -56,6 +56,8 @@ def traverse_tree(state, parent, graph): if state.has_key('Children'): # Add all array nodes as children of this node. for child_name, child_state in state['Children']: + if child_name in NODE_BLACKLIST: + continue child = pydot.Node(str(NEXT_NODE_ID)) NEXT_NODE_ID+=1 child.set_comment(child_name) @@ -75,8 +77,7 @@ if __name__ == '__main__': args = parser.parse_args() - u = Unity() - introspection_tree = u.get_state() + introspection_tree = get_state_by_path('/') graph = pydot.Dot() graph.set_simplify(False) graph.set_node_defaults(shape='Mrecord') |
