theme: finish titleLayout implementation (#2150)

Change Openbox style WLIMC syntax to `menu:iconify,max,close` as was
agreed when PR #2088 was merged.

    <titlebar>
      <layout>menu:iconify,max,close</layout>
      <showTitle>yes|on</showTitle>
    </titlebar>

Ref:
- https://github.com/labwc/labwc/pull/2088#issuecomment-2295730704
This commit is contained in:
Johan Malm 2024-09-23 19:17:12 +01:00 committed by GitHub
parent 04a145f89a
commit cc26ac29b9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 140 additions and 52 deletions

View file

@ -437,19 +437,24 @@ extending outward from the snapped edge.
*<theme><name>*
The name of the Openbox theme to use. It is not set by default.
*<theme><titleLayout>*
*<theme><titlebar><layout>*
Selection and order of buttons in a window's titlebar.
The following letters can be used, each only once:
- L: window label (aka. title)
- |: empty space (can be used instead of a title)
- W: window menu
- I: iconify
- M: maximize toggle
- C: close
- S: shade toggle
- D: all-desktops toggle
The following identifiers can be used, each only once:
- 'menu': window menu
- 'iconify': iconify
- 'max': maximize toggle
- 'close': close
- 'shade': shade toggle
- 'desk': all-desktops toggle
Example: WLIMC
A colon deliminator is used to separate buttons on the left and right,
whereas commas are used to separate items within a section. It is
mandatory to use one colon.
Default: menu:iconify,max,close
*<theme><titlebar><showTitle>* [yes|no]
Show the window title in the titlebar. Default is yes.
*<theme><cornerRadius>*
The radius of server side decoration top corners. Default is 8.

View file

@ -28,7 +28,10 @@
<!-- <font><theme> can be defined without an attribute to set all places -->
<theme>
<name></name>
<titleLayout>WLIMC</titleLayout>
<titlebar>
<layout>menu:iconify,max,close</layout>
<showTitle>yes</showTitle>
</titlebar>
<cornerRadius>8</cornerRadius>
<keepBorder>yes</keepBorder>
<dropShadows>no</dropShadows>

View file

@ -119,61 +119,139 @@ parse_window_type(const char *type)
}
}
/*
* Openbox/labwc comparison
*
* Instead of openbox's <titleLayout>WLIMC</title> we use
*
* <titlebar>
* <layout>menu:iconfiy,max,close</layout>
* <showTitle>yes|no</showTitle>
* </titlebar>
*
* ...using the icon names (like iconify.xbm) without the file extension for the
* identifier.
*
* labwc openbox description
* ----- ------- -----------
* menu W Open window menu (client-menu)
* iconfiy I Iconify (aka minimize)
* max M Maximize toggle
* close C Close
* shade S Shade toggle
* desk D All-desktops toggle (aka omnipresent)
*/
static void
fill_title_layout(char *nodename, char *content)
fill_section(const char *content, struct wl_list *list)
{
char *c, *c2;
enum ssd_part_type type;
struct title_button *item;
struct wl_list *list = &rc.title_buttons_left;
for (c = content; *c != '\0'; c++) {
for (c2 = content; c2 < c; c2++) {
if (*c2 == *c) {
break;
}
}
if (c2 != c) {
gchar **identifiers = g_strsplit(content, ",", -1);
for (size_t i = 0; identifiers[i]; ++i) {
char *identifier = identifiers[i];
if (string_null_or_empty(identifier)) {
continue;
}
switch (*c) {
/* case 'N': icon */
case 'L':
list = &rc.title_buttons_right;
rc.show_title = true;
continue;
case '|':
list = &rc.title_buttons_right;
continue;
case 'W':
enum ssd_part_type type = LAB_SSD_NONE;
if (!strcmp(identifier, "menu")) {
type = LAB_SSD_BUTTON_WINDOW_MENU;
break;
case 'I':
} else if (!strcmp(identifier, "iconify")) {
type = LAB_SSD_BUTTON_ICONIFY;
break;
case 'M':
} else if (!strcmp(identifier, "max")) {
type = LAB_SSD_BUTTON_MAXIMIZE;
break;
case 'C':
} else if (!strcmp(identifier, "close")) {
type = LAB_SSD_BUTTON_CLOSE;
break;
case 'S':
} else if (!strcmp(identifier, "shade")) {
type = LAB_SSD_BUTTON_SHADE;
break;
case 'D':
} else if (!strcmp(identifier, "desk")) {
type = LAB_SSD_BUTTON_OMNIPRESENT;
break;
default:
} else {
wlr_log(WLR_ERROR, "invalid titleLayout identifier '%s'",
identifier);
continue;
}
item = znew(*item);
assert(type != LAB_SSD_NONE);
struct title_button *item = znew(*item);
item->type = type;
wl_list_append(list, &item->link);
}
g_strfreev(identifiers);
}
static int
compare_strings(const void *a, const void *b)
{
char * const *str1 = a;
char * const *str2 = b;
return strcmp(*str1, *str2);
}
static bool
contains_duplicates(char *content)
{
bool ret = false;
/*
* The string typically looks like: 'menu:iconfiy,max,close' so we have
* to split on both ':' and ','.
*/
gchar **idents = g_strsplit_set(content, ",:", -1);
/*
* We've got to have at least two for duplicates to exist. Bailing out
* early here also enables the below algorithm which just iterates and
* checks if previous item is the same.
*/
if (g_strv_length(idents) <= 1) {
goto out;
}
qsort(idents, g_strv_length(idents), sizeof(gchar *), compare_strings);
for (size_t i = 1; idents[i]; ++i) {
if (string_null_or_empty(idents[i])) {
continue;
}
if (!strcmp(idents[i], idents[i-1])) {
ret = true;
wlr_log(WLR_ERROR,
"titleLayout identifier '%s' is a duplicate",
idents[i]);
break;
}
}
out:
g_strfreev(idents);
return ret;
}
static void
fill_title_layout(char *content)
{
if (contains_duplicates(content)) {
wlr_log(WLR_ERROR, "titleLayout contains duplicates");
return;
}
struct wl_list *sections[] = {
&rc.title_buttons_left,
&rc.title_buttons_right,
};
gchar **parts = g_strsplit(content, ":", -1);
if (g_strv_length(parts) != 2) {
wlr_log(WLR_ERROR, "<titlebar><layout> must contain one colon");
goto err;
}
for (size_t i = 0; parts[i]; ++i) {
fill_section(parts[i], sections[i]);
}
rc.title_layout_loaded = true;
err:
g_strfreev(parts);
}
static void
@ -989,8 +1067,10 @@ entry(xmlNode *node, char *nodename, char *content)
rc.placement_cascade_offset_y = atoi(content);
} else if (!strcmp(nodename, "name.theme")) {
rc.theme_name = xstrdup(content);
} else if (!strcmp(nodename, "titlelayout.theme")) {
fill_title_layout(nodename, content);
} else if (!strcasecmp(nodename, "layout.titlebar.theme")) {
fill_title_layout(content);
} else if (!strcasecmp(nodename, "showTitle.titlebar.theme")) {
rc.show_title = parse_bool(content, true);
} else if (!strcmp(nodename, "cornerradius.theme")) {
rc.corner_radius = atoi(content);
} else if (!strcasecmp(nodename, "keepBorder.theme")) {
@ -1578,7 +1658,7 @@ post_processing(void)
}
if (!rc.title_layout_loaded) {
fill_title_layout("titlelayout.theme", "WLIMC");
fill_title_layout("menu:iconify,max,close");
}
/*