UI: Ctrl+Tab and Ctrl+Shift+Tab to cycle through space context "tabs"
authorJulian Eisel <eiseljulian@gmail.com>
Mon, 5 Sep 2016 14:58:40 +0000 (16:58 +0200)
committerJulian Eisel <eiseljulian@gmail.com>
Mon, 5 Sep 2016 15:28:41 +0000 (17:28 +0200)
In User Preferences, Properties Editor and toolshelf, Ctrl+Tab and Ctrl+Shift+Tab now activates the next or previous space context (or category in case of toolshelf tabs), respectively.

For Properties Editor such functionality was completely missing, only toolshelf allowed cycling using ctrl+mousewheel (or only mousewheel while hovering tab region). Ctrl+Tab and Ctrl+Shift+Tab are common web browser shortcuts, so they're a reasonable choice to go with.
Reaching the first/last item doesn't cause the cycling to stop, we continue at the other end of the list then. (I didn't add this to Ctrl+Mousewheel toggling in toolshelf since I wanted to keep its behavior unchanged.)

We could get rid of (Ctrl+)Mousewheel cycling in toolshelf, but this may break user habits.

The cycling happens using a new operator, UI_OT_space_context_cycle, for toolshelf tabs it's hardcoded in panel handling code though.
Generalized rna_property_enum_step a bit and moved it to rna_access.c to allow external reuse.

Reviewed By: venomgfx
Differential Revision: https://developer.blender.org/D2189

source/blender/editors/include/UI_interface.h
source/blender/editors/interface/interface_ops.c
source/blender/editors/interface/interface_panel.c
source/blender/editors/interface/interface_regions.c
source/blender/editors/screen/screen_ops.c
source/blender/makesrna/RNA_access.h
source/blender/makesrna/intern/rna_access.c

index 49e5845e3ca34cb341df3f9fd1f5cd474382db07..26a6fdd7d1f6c6043286ce3717a98799730d0fa0 100644 (file)
@@ -1021,6 +1021,12 @@ void ED_keymap_ui(struct wmKeyConfig *keyconf);
 void UI_drop_color_copy(struct wmDrag *drag, struct wmDropBox *drop);
 int UI_drop_color_poll(struct bContext *C, struct wmDrag *drag, const struct wmEvent *event);
 
+/* UI_OT_space_context_cycle direction */
+enum {
+       SPACE_CONTEXT_CYCLE_PREV,
+       SPACE_CONTEXT_CYCLE_NEXT,
+};
+
 bool UI_context_copy_to_selected_list(
         struct bContext *C, struct PointerRNA *ptr, struct PropertyRNA *prop,
         struct ListBase *r_lb, bool *r_use_path_from_id, char **r_path);
index 1af6d902b1868ac08dd8667c6d5b23f7e904ba43..cb539bb1c5d2a062eea43d1b3931105264dec535 100644 (file)
@@ -1082,6 +1082,78 @@ static void UI_OT_drop_color(wmOperatorType *ot)
        RNA_def_boolean(ot->srna, "gamma", 0, "Gamma Corrected", "The source color is gamma corrected ");
 }
 
+/* ------------------------------------------------------------------------- */
+
+static EnumPropertyItem space_context_cycle_direction[] = {
+       {SPACE_CONTEXT_CYCLE_PREV, "PREV", 0, "Previous", ""},
+       {SPACE_CONTEXT_CYCLE_NEXT, "NEXT", 0, "Next", ""},
+       {0, NULL, 0, NULL, NULL}
+};
+
+static int space_context_cycle_poll(bContext *C)
+{
+       ScrArea *sa = CTX_wm_area(C);
+       return ELEM(sa->spacetype, SPACE_BUTS, SPACE_USERPREF);
+}
+
+/**
+ * Helper to get the correct RNA pointer/property pair for changing
+ * the display context of active space type in \sa.
+ */
+static void context_cycle_prop_get(
+        bScreen *screen, const ScrArea *sa,
+        PointerRNA *r_ptr, PropertyRNA **r_prop)
+{
+       const char *propname;
+
+       switch (sa->spacetype) {
+               case SPACE_BUTS:
+                       RNA_pointer_create(&screen->id, &RNA_SpaceProperties, sa->spacedata.first, r_ptr);
+                       propname = "context";
+                       break;
+               case SPACE_USERPREF:
+                       RNA_pointer_create(NULL, &RNA_UserPreferences, &U, r_ptr);
+                       propname = "active_section";
+                       break;
+       }
+
+       *r_prop = RNA_struct_find_property(r_ptr, propname);
+}
+
+static int space_context_cycle_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(event))
+{
+       const int direction = RNA_enum_get(op->ptr, "direction");
+
+       PointerRNA ptr;
+       PropertyRNA *prop;
+       context_cycle_prop_get(CTX_wm_screen(C), CTX_wm_area(C), &ptr, &prop);
+
+       const int old_context = RNA_property_enum_get(&ptr, prop);
+       const int new_context = RNA_property_enum_step(
+                         C, &ptr, prop, old_context,
+                         direction == SPACE_CONTEXT_CYCLE_PREV ? -1 : 1);
+       RNA_property_enum_set(&ptr, prop, new_context);
+       RNA_property_update(C, &ptr, prop);
+
+       return OPERATOR_FINISHED;
+}
+
+static void UI_OT_space_context_cycle(wmOperatorType *ot)
+{
+       /* identifiers */
+       ot->name = "Cycle Space Context";
+       ot->description = "Cycle through the editor context by activating the next/previous one";
+       ot->idname = "UI_OT_space_context_cycle";
+
+       /* api callbacks */
+       ot->invoke = space_context_cycle_invoke;
+       ot->poll = space_context_cycle_poll;
+
+       ot->flag = 0;
+
+       RNA_def_enum(ot->srna, "direction", space_context_cycle_direction, SPACE_CONTEXT_CYCLE_NEXT, "Direction",
+                    "Direction to cycle through");
+}
 
 
 /* ********************************************************* */
@@ -1102,6 +1174,7 @@ void ED_operatortypes_ui(void)
        WM_operatortype_append(UI_OT_edittranslation_init);
 #endif
        WM_operatortype_append(UI_OT_reloadtranslation);
+       WM_operatortype_append(UI_OT_space_context_cycle);
 
        /* external */
        WM_operatortype_append(UI_OT_eyedropper_color);
index 79961eae79d602f38134e7796a5aa64ffa60bb66..c131bcb8e14cd4ccadfbc02071c403029b021058 100644 (file)
@@ -1841,6 +1841,52 @@ void UI_panel_category_draw_all(ARegion *ar, const char *category_id_active)
 #undef USE_FLAT_INACTIVE
 }
 
+static int ui_handle_panel_category_cycling(const wmEvent *event, ARegion *ar, const uiBut *active_but)
+{
+       const bool is_mousewheel = ELEM(event->type, WHEELUPMOUSE, WHEELDOWNMOUSE);
+       const bool inside_tabregion = (event->mval[0] < ((PanelCategoryDyn *)ar->panels_category.first)->rect.xmax);
+
+       /* if mouse is inside non-tab region, ctrl key is required */
+       if (is_mousewheel && !event->ctrl && !inside_tabregion)
+               return WM_UI_HANDLER_CONTINUE;
+
+
+       if (active_but && ui_but_supports_cycling(active_but)) {
+               /* skip - exception to make cycling buttons
+                * using ctrl+mousewheel work in tabbed regions */
+       }
+       else {
+               const char *category = UI_panel_category_active_get(ar, false);
+               if (LIKELY(category)) {
+                       PanelCategoryDyn *pc_dyn = UI_panel_category_find(ar, category);
+                       if (LIKELY(pc_dyn)) {
+                               if (is_mousewheel) {
+                                       /* we can probably get rid of this and only allow ctrl+tabbing */
+                                       pc_dyn = (event->type == WHEELDOWNMOUSE) ? pc_dyn->next : pc_dyn->prev;
+                               }
+                               else {
+                                       const bool backwards = event->shift;
+                                       pc_dyn = backwards ? pc_dyn->prev : pc_dyn->next;
+                                       if (!pc_dyn) {
+                                               /* proper cyclic behavior, back to first/last category (only used for ctrl+tab) */
+                                               pc_dyn = backwards ? ar->panels_category.last : ar->panels_category.first;
+                                       }
+                               }
+
+                               if (pc_dyn) {
+                                       /* intentionally don't reset scroll in this case,
+                                        * this allows for quick browsing between tabs */
+                                       UI_panel_category_active_set(ar, pc_dyn->idname);
+                                       ED_region_tag_redraw(ar);
+                               }
+                       }
+               }
+               return WM_UI_HANDLER_BREAK;
+       }
+
+       return WM_UI_HANDLER_CONTINUE;
+}
+
 /* XXX should become modal keymap */
 /* AKey is opening/closing panels, independent of button state now */
 
@@ -1853,6 +1899,7 @@ int ui_handler_panel_region(bContext *C, const wmEvent *event, ARegion *ar, cons
 
        retval = WM_UI_HANDLER_CONTINUE;
 
+       /* handle category tabs */
        if (has_category_tabs) {
                if (event->val == KM_PRESS) {
                        if (event->type == LEFTMOUSE) {
@@ -1867,32 +1914,9 @@ int ui_handler_panel_region(bContext *C, const wmEvent *event, ARegion *ar, cons
                                        retval = WM_UI_HANDLER_BREAK;
                                }
                        }
-                       else if (ELEM(event->type, WHEELUPMOUSE, WHEELDOWNMOUSE)) {
-                               /* mouse wheel cycle tabs */
-
-                               /* first check if the mouse is in the tab region */
-                               if (event->ctrl || (event->mval[0] < ((PanelCategoryDyn *)ar->panels_category.first)->rect.xmax)) {
-                                       if (active_but && ui_but_supports_cycling(active_but)) {
-                                               /* skip - exception to make cycling buttons
-                                                * using ctrl+mousewheel work in tabbed regions */
-                                       }
-                                       else {
-                                               const char *category = UI_panel_category_active_get(ar, false);
-                                               if (LIKELY(category)) {
-                                                       PanelCategoryDyn *pc_dyn = UI_panel_category_find(ar, category);
-                                                       if (LIKELY(pc_dyn)) {
-                                                               pc_dyn = (event->type == WHEELDOWNMOUSE) ? pc_dyn->next : pc_dyn->prev;
-                                                               if (pc_dyn) {
-                                                                       /* intentionally don't reset scroll in this case,
-                                                                        * this allows for quick browsing between tabs */
-                                                                       UI_panel_category_active_set(ar, pc_dyn->idname);
-                                                                       ED_region_tag_redraw(ar);
-                                                               }
-                                                       }
-                                               }
-                                               retval = WM_UI_HANDLER_BREAK;
-                                       }
-                               }
+                       else if ((event->type == TABKEY && event->ctrl) || ELEM(event->type, WHEELUPMOUSE, WHEELDOWNMOUSE)) {
+                               /* cycle tabs */
+                               retval = ui_handle_panel_category_cycling(event, ar, active_but);
                        }
                }
        }
index c507401b9a071c9844e3cc08a80cd39f0d62a31f..4ea5e2092b6de909ad2835eebe44c1a0400af700 100644 (file)
 #define MENU_PADDING           (int)(0.2f * UI_UNIT_Y)
 #define MENU_BORDER                    (int)(0.3f * U.widget_unit)
 
-static int rna_property_enum_step(const bContext *C, PointerRNA *ptr, PropertyRNA *prop, int direction)
-{
-       EnumPropertyItem *item_array;
-       int totitem;
-       bool free;
-       int value;
-       int i, i_init;
-       int step = (direction < 0) ? -1 : 1;
-       int step_tot = 0;
-
-       RNA_property_enum_items((bContext *)C, ptr, prop, &item_array, &totitem, &free);
-       value = RNA_property_enum_get(ptr, prop);
-       i = RNA_enum_from_value(item_array, value);
-       i_init = i;
-
-       do {
-               i = mod_i(i + step, totitem);
-               if (item_array[i].identifier[0]) {
-                       step_tot += step;
-               }
-       } while ((i != i_init) && (step_tot != direction));
-
-       if (i != i_init) {
-               value = item_array[i].value;
-       }
-
-       if (free) {
-               MEM_freeN(item_array);
-       }
-
-       return value;
-}
 
 bool ui_but_menu_step_poll(const uiBut *but)
 {
@@ -122,7 +90,8 @@ int ui_but_menu_step(uiBut *but, int direction)
                        return but->menu_step_func(but->block->evil_C, direction, but->poin);
                }
                else {
-                       return rna_property_enum_step(but->block->evil_C, &but->rnapoin, but->rnaprop, direction);
+                       const int curval = RNA_property_enum_get(&but->rnapoin, but->rnaprop);
+                       return RNA_property_enum_step(but->block->evil_C, &but->rnapoin, but->rnaprop, curval, direction);
                }
        }
 
index 6dbb5db53d0aac2b9ed82a838077dda4ffc05e39..e446194a1dae6423f7bd8df079f3d2efb0bf38a0 100644 (file)
@@ -4331,7 +4331,13 @@ void ED_keymap_screen(wmKeyConfig *keyconf)
 
        WM_keymap_add_item(keymap, "SCREEN_OT_screenshot", F3KEY, KM_PRESS, KM_CTRL, 0);
        WM_keymap_add_item(keymap, "SCREEN_OT_screencast", F3KEY, KM_PRESS, KM_ALT, 0);
-       
+
+       /* UI */
+       kmi = WM_keymap_add_item(keymap, "UI_OT_space_context_cycle", TABKEY, KM_PRESS, KM_CTRL, 0);
+       RNA_enum_set(kmi->ptr, "direction", SPACE_CONTEXT_CYCLE_NEXT);
+       kmi = WM_keymap_add_item(keymap, "UI_OT_space_context_cycle", TABKEY, KM_PRESS, KM_CTRL | KM_SHIFT, 0);
+       RNA_enum_set(kmi->ptr, "direction", SPACE_CONTEXT_CYCLE_PREV);
+
        /* tests */
        WM_keymap_add_item(keymap, "SCREEN_OT_region_quadview", QKEY, KM_PRESS, KM_CTRL | KM_ALT, 0);
        WM_keymap_verify_item(keymap, "SCREEN_OT_repeat_history", F3KEY, KM_PRESS, 0, 0);
index 9cbe132282fcb6e3a6196aac50715191e76cf142..820465ee7d186a276a78a13ce684d0afdd15ab17 100644 (file)
@@ -916,6 +916,7 @@ int RNA_property_enum_get(PointerRNA *ptr, PropertyRNA *prop);
 void RNA_property_enum_set(PointerRNA *ptr, PropertyRNA *prop, int value);
 int RNA_property_enum_get_default(PointerRNA *ptr, PropertyRNA *prop);
 void *RNA_property_enum_py_data_get(PropertyRNA *prop);
+int RNA_property_enum_step(const struct bContext *C, PointerRNA *ptr, PropertyRNA *prop, int from_value, int direction);
 
 PointerRNA RNA_property_pointer_get(PointerRNA *ptr, PropertyRNA *prop);
 void RNA_property_pointer_set(PointerRNA *ptr, PropertyRNA *prop, PointerRNA ptr_value);
index 047e5ea17ab72488ac0f5d1702a8769ddc67e0d2..6879a0534e9ea2dcafa9a6f9286f3d02078e6322 100644 (file)
@@ -2873,6 +2873,45 @@ void *RNA_property_enum_py_data_get(PropertyRNA *prop)
        return eprop->py_data;
 }
 
+/**
+ * Get the value of the item that is \a step items away from \a from_value.
+ *
+ * \param from_value: Item value to start stepping from.
+ * \param step: Absolute value defines step size, sign defines direction.
+ *              E.g to get the next item, pass 1, for the previous -1.
+ */
+int RNA_property_enum_step(const bContext *C, PointerRNA *ptr, PropertyRNA *prop, int from_value, int step)
+{
+       EnumPropertyItem *item_array;
+       int totitem;
+       bool free;
+       int result_value = from_value;
+       int i, i_init;
+       int single_step = (step < 0) ? -1 : 1;
+       int step_tot = 0;
+
+       RNA_property_enum_items((bContext *)C, ptr, prop, &item_array, &totitem, &free);
+       i = RNA_enum_from_value(item_array, from_value);
+       i_init = i;
+
+       do {
+               i = mod_i(i + single_step, totitem);
+               if (item_array[i].identifier[0]) {
+                       step_tot += single_step;
+               }
+       } while ((i != i_init) && (step_tot != step));
+
+       if (i != i_init) {
+               result_value = item_array[i].value;
+       }
+
+       if (free) {
+               MEM_freeN(item_array);
+       }
+
+       return result_value;
+}
+
 PointerRNA RNA_property_pointer_get(PointerRNA *ptr, PropertyRNA *prop)
 {
        PointerPropertyRNA *pprop = (PointerPropertyRNA *)prop;