diff --git a/WINGs/Makefile.am b/WINGs/Makefile.am index 942c619c..5f8fa51a 100644 --- a/WINGs/Makefile.am +++ b/WINGs/Makefile.am @@ -41,7 +41,6 @@ libWINGs_la_SOURCES = \ wevent.c \ wfilepanel.c \ wframe.c \ - wfontpanel.c \ widgets.c \ winputmethod.c \ wlabel.c \ diff --git a/WINGs/Tests/Makefile.am b/WINGs/Tests/Makefile.am index 98f0e458..585c0e5b 100644 --- a/WINGs/Tests/Makefile.am +++ b/WINGs/Tests/Makefile.am @@ -4,14 +4,17 @@ AUTOMAKE_OPTIONS = noinst_PROGRAMS = wtest wmquery wmfile testmywidget -LDADD= $(top_builddir)/WINGs/libWINGs.la $(top_builddir)/wrlib/libwraster.la \ +LDADD= $(top_builddir)/WINGs/libWINGs.la \ + $(top_builddir)/wrlib/libwraster.la \ $(top_builddir)/WINGs/libWUtil.la \ - @XFT_LIBS@ @INTLIBS@ @XLIBS@ + $(top_builddir)/WINGs/wings-rs/target/debug/libwings_rs.a \ + @XFT_LIBS@ @INTLIBS@ @XLIBS@ @FCLIBS@ @PANGO_LIBS@ testmywidget_SOURCES = testmywidget.c mywidget.c mywidget.h -wtest_DEPENDENCIES = $(top_builddir)/WINGs/libWINGs.la +wtest_DEPENDENCIES = $(top_builddir)/WINGs/libWINGs.la \ + $(top_builddir)/WINGs/wings-rs/target/debug/libwings_rs.a EXTRA_DIST = logo.xpm upbtn.xpm wm.html wm.png diff --git a/WINGs/WINGs/WINGs.h b/WINGs/WINGs/WINGs.h index b1ecad12..1d1a2927 100644 --- a/WINGs/WINGs/WINGs.h +++ b/WINGs/WINGs/WINGs.h @@ -132,7 +132,7 @@ typedef enum { } WMButtonType; /* button behaviour masks */ -enum { +typedef enum { WBBSpringLoadedMask = (1 << 0), WBBPushInMask = (1 << 1), WBBPushChangeMask = (1 << 2), @@ -140,7 +140,7 @@ enum { WBBStateLightMask = (1 << 5), WBBStateChangeMask = (1 << 6), WBBStatePushMask = (1 << 7) -}; +} WMButtonBehaviorMask; /* frame title positions */ diff --git a/WINGs/WINGs/WINGsP.h.in b/WINGs/WINGs/WINGsP.h.in index 56829a92..b7ed025a 100644 --- a/WINGs/WINGs/WINGsP.h.in +++ b/WINGs/WINGs/WINGsP.h.in @@ -122,8 +122,6 @@ typedef struct W_Screen { WMOpenPanel *sharedOpenPanel; WMSavePanel *sharedSavePanel; - struct W_FontPanel *sharedFontPanel; - struct W_ColorPanel *sharedColorPanel; Pixmap stipple; diff --git a/WINGs/wfontpanel.c b/WINGs/wfontpanel.c deleted file mode 100644 index 9b5b001d..00000000 --- a/WINGs/wfontpanel.c +++ /dev/null @@ -1,825 +0,0 @@ - -#include "WINGsP.h" -#include "WUtil.h" -#include "wconfig.h" - -#include -#include -#include -#include - -#include -#include - -/* XXX TODO */ -char *WMFontPanelFontChangedNotification = "WMFontPanelFontChangedNotification"; - -typedef struct W_FontPanel { - WMWindow *win; - - WMFrame *upperF; - WMTextField *sampleT; - - WMSplitView *split; - - WMFrame *lowerF; - WMLabel *famL; - WMList *famLs; - WMLabel *typL; - WMList *typLs; - WMLabel *sizL; - WMTextField *sizT; - WMList *sizLs; - - WMAction2 *action; - void *data; - - WMButton *revertB; - WMButton *setB; - - WMPropList *fdb; -} FontPanel; - -#define MIN_UPPER_HEIGHT 20 -#define MIN_LOWER_HEIGHT 140 - -#define BUTTON_SPACE_HEIGHT 40 - -#define MIN_WIDTH 250 -#define MIN_HEIGHT (MIN_UPPER_HEIGHT+MIN_LOWER_HEIGHT+BUTTON_SPACE_HEIGHT) - -#define DEF_UPPER_HEIGHT 60 -#define DEF_LOWER_HEIGHT 310 - -#define DEF_WIDTH 320 -#define DEF_HEIGHT (DEF_UPPER_HEIGHT+DEF_LOWER_HEIGHT) - -static const int scalableFontSizes[] = { - 8, - 10, - 11, - 12, - 14, - 16, - 18, - 20, - 24, - 36, - 48, - 64 -}; - -static void setFontPanelFontName(FontPanel * panel, const char *family, const char *style, double size); - -static int isXLFD(const char *font, int *length_ret); - -static void arrangeLowerFrame(FontPanel * panel); - -static void familyClick(WMWidget *, void *); -static void typefaceClick(WMWidget *, void *); -static void sizeClick(WMWidget *, void *); - -static void listFamilies(WMScreen * scr, WMFontPanel * panel); - -static void splitViewConstrainCallback(WMSplitView * sPtr, int indView, int *min, int *max) -{ - /* Parameter not used, but tell the compiler that it is ok */ - (void) sPtr; - (void) max; - - if (indView == 0) - *min = MIN_UPPER_HEIGHT; - else - *min = MIN_LOWER_HEIGHT; -} - -static void notificationObserver(void *self, WMNotification * notif) -{ - WMFontPanel *panel = (WMFontPanel *) self; - void *object = WMGetNotificationObject(notif); - - if (WMGetNotificationName(notif) == WMViewSizeDidChangeNotification) { - if (object == WMWidgetView(panel->win)) { - int h = WMWidgetHeight(panel->win); - int w = WMWidgetWidth(panel->win); - - WMResizeWidget(panel->split, w, h - BUTTON_SPACE_HEIGHT); - - WMMoveWidget(panel->setB, w - 80, h - (BUTTON_SPACE_HEIGHT - 5)); - - WMMoveWidget(panel->revertB, w - 240, h - (BUTTON_SPACE_HEIGHT - 5)); - - } else if (object == WMWidgetView(panel->upperF)) { - - if (WMWidgetHeight(panel->upperF) < MIN_UPPER_HEIGHT) { - WMResizeWidget(panel->upperF, WMWidgetWidth(panel->upperF), MIN_UPPER_HEIGHT); - } else { - WMResizeWidget(panel->sampleT, WMWidgetWidth(panel->upperF) - 20, - WMWidgetHeight(panel->upperF) - 10); - } - - } else if (object == WMWidgetView(panel->lowerF)) { - - if (WMWidgetHeight(panel->lowerF) < MIN_LOWER_HEIGHT) { - WMResizeWidget(panel->upperF, WMWidgetWidth(panel->upperF), MIN_UPPER_HEIGHT); - - WMMoveWidget(panel->lowerF, 0, WMWidgetHeight(panel->upperF) - + WMGetSplitViewDividerThickness(panel->split)); - - WMResizeWidget(panel->lowerF, WMWidgetWidth(panel->lowerF), - WMWidgetWidth(panel->split) - MIN_UPPER_HEIGHT - - WMGetSplitViewDividerThickness(panel->split)); - } else { - arrangeLowerFrame(panel); - } - } - } -} - -static void closeWindow(WMWidget * w, void *data) -{ - FontPanel *panel = (FontPanel *) data; - - /* Parameter not used, but tell the compiler that it is ok */ - (void) w; - - WMHideFontPanel(panel); -} - -static void setClickedAction(WMWidget * w, void *data) -{ - FontPanel *panel = (FontPanel *) data; - - /* Parameter not used, but tell the compiler that it is ok */ - (void) w; - - if (panel->action) - (*panel->action) (panel, panel->data); -} - -static void revertClickedAction(WMWidget * w, void *data) -{ - /* Parameter not used, but tell the compiler that it is ok */ - (void) w; - (void) data; - - /*FontPanel *panel = (FontPanel*)data; */ - /* XXX TODO */ -} - -WMFontPanel *WMGetFontPanel(WMScreen * scr) -{ - FontPanel *panel; - WMColor *dark, *white; - WMFont *font; - int divThickness; - - if (scr->sharedFontPanel) - return scr->sharedFontPanel; - - panel = wmalloc(sizeof(FontPanel)); - - panel->win = WMCreateWindow(scr, "fontPanel"); - /* WMSetWidgetBackgroundColor(panel->win, WMWhiteColor(scr)); */ - WMSetWindowTitle(panel->win, _("Font Panel")); - WMResizeWidget(panel->win, DEF_WIDTH, DEF_HEIGHT); - WMSetWindowMinSize(panel->win, MIN_WIDTH, MIN_HEIGHT); - WMSetViewNotifySizeChanges(WMWidgetView(panel->win), True); - - WMSetWindowCloseAction(panel->win, closeWindow, panel); - - panel->split = WMCreateSplitView(panel->win); - WMResizeWidget(panel->split, DEF_WIDTH, DEF_HEIGHT - BUTTON_SPACE_HEIGHT); - WMSetSplitViewConstrainProc(panel->split, splitViewConstrainCallback); - - divThickness = WMGetSplitViewDividerThickness(panel->split); - - panel->upperF = WMCreateFrame(panel->win); - WMSetFrameRelief(panel->upperF, WRFlat); - WMSetViewNotifySizeChanges(WMWidgetView(panel->upperF), True); - panel->lowerF = WMCreateFrame(panel->win); - /* WMSetWidgetBackgroundColor(panel->lowerF, WMBlackColor(scr)); */ - WMSetFrameRelief(panel->lowerF, WRFlat); - WMSetViewNotifySizeChanges(WMWidgetView(panel->lowerF), True); - - WMAddSplitViewSubview(panel->split, W_VIEW(panel->upperF)); - WMAddSplitViewSubview(panel->split, W_VIEW(panel->lowerF)); - - WMResizeWidget(panel->upperF, DEF_WIDTH, DEF_UPPER_HEIGHT); - - WMResizeWidget(panel->lowerF, DEF_WIDTH, DEF_LOWER_HEIGHT); - - WMMoveWidget(panel->lowerF, 0, 60 + divThickness); - - white = WMWhiteColor(scr); - dark = WMDarkGrayColor(scr); - - panel->sampleT = WMCreateTextField(panel->upperF); - WMResizeWidget(panel->sampleT, DEF_WIDTH - 20, 50); - WMMoveWidget(panel->sampleT, 10, 10); - WMSetTextFieldText(panel->sampleT, _("The quick brown fox jumps over the lazy dog")); - - font = WMBoldSystemFontOfSize(scr, 12); - - panel->famL = WMCreateLabel(panel->lowerF); - WMSetWidgetBackgroundColor(panel->famL, dark); - WMSetLabelText(panel->famL, _("Family")); - WMSetLabelFont(panel->famL, font); - WMSetLabelTextColor(panel->famL, white); - WMSetLabelRelief(panel->famL, WRSunken); - WMSetLabelTextAlignment(panel->famL, WACenter); - - panel->famLs = WMCreateList(panel->lowerF); - WMSetListAction(panel->famLs, familyClick, panel); - - panel->typL = WMCreateLabel(panel->lowerF); - WMSetWidgetBackgroundColor(panel->typL, dark); - WMSetLabelText(panel->typL, _("Typeface")); - WMSetLabelFont(panel->typL, font); - WMSetLabelTextColor(panel->typL, white); - WMSetLabelRelief(panel->typL, WRSunken); - WMSetLabelTextAlignment(panel->typL, WACenter); - - panel->typLs = WMCreateList(panel->lowerF); - WMSetListAction(panel->typLs, typefaceClick, panel); - - panel->sizL = WMCreateLabel(panel->lowerF); - WMSetWidgetBackgroundColor(panel->sizL, dark); - WMSetLabelText(panel->sizL, _("Size")); - WMSetLabelFont(panel->sizL, font); - WMSetLabelTextColor(panel->sizL, white); - WMSetLabelRelief(panel->sizL, WRSunken); - WMSetLabelTextAlignment(panel->sizL, WACenter); - - panel->sizT = WMCreateTextField(panel->lowerF); - /* WMSetTextFieldAlignment(panel->sizT, WARight); */ - - panel->sizLs = WMCreateList(panel->lowerF); - WMSetListAction(panel->sizLs, sizeClick, panel); - - WMReleaseFont(font); - WMReleaseColor(white); - WMReleaseColor(dark); - - panel->setB = WMCreateCommandButton(panel->win); - WMResizeWidget(panel->setB, 70, 24); - WMMoveWidget(panel->setB, 240, DEF_HEIGHT - (BUTTON_SPACE_HEIGHT - 5)); - WMSetButtonText(panel->setB, _("Set")); - WMSetButtonAction(panel->setB, setClickedAction, panel); - - panel->revertB = WMCreateCommandButton(panel->win); - WMResizeWidget(panel->revertB, 70, 24); - WMMoveWidget(panel->revertB, 80, DEF_HEIGHT - (BUTTON_SPACE_HEIGHT - 5)); - WMSetButtonText(panel->revertB, _("Revert")); - WMSetButtonAction(panel->revertB, revertClickedAction, panel); - - WMRealizeWidget(panel->win); - - WMMapSubwidgets(panel->upperF); - WMMapSubwidgets(panel->lowerF); - WMMapSubwidgets(panel->split); - WMMapSubwidgets(panel->win); - - WMUnmapWidget(panel->revertB); - - arrangeLowerFrame(panel); - - scr->sharedFontPanel = panel; - - /* register notification observers */ - WMAddNotificationObserver(notificationObserver, panel, - WMViewSizeDidChangeNotification, WMWidgetView(panel->win)); - WMAddNotificationObserver(notificationObserver, panel, - WMViewSizeDidChangeNotification, WMWidgetView(panel->upperF)); - WMAddNotificationObserver(notificationObserver, panel, - WMViewSizeDidChangeNotification, WMWidgetView(panel->lowerF)); - - listFamilies(scr, panel); - - return panel; -} - -void WMFreeFontPanel(WMFontPanel * panel) -{ - if (panel == WMWidgetScreen(panel->win)->sharedFontPanel) { - WMWidgetScreen(panel->win)->sharedFontPanel = NULL; - } - WMRemoveNotificationObserver(panel); - WMUnmapWidget(panel->win); - WMDestroyWidget(panel->win); - wfree(panel); -} - -void WMShowFontPanel(WMFontPanel * panel) -{ - WMMapWidget(panel->win); -} - -void WMHideFontPanel(WMFontPanel * panel) -{ - WMUnmapWidget(panel->win); -} - -WMFont *WMGetFontPanelFont(WMFontPanel * panel) -{ - return WMGetTextFieldFont(panel->sampleT); -} - -void WMSetFontPanelFont(WMFontPanel * panel, const char *fontName) -{ - int fname_len; - FcPattern *pattern; - FcChar8 *family, *style; - double size; - - if (!isXLFD(fontName, &fname_len)) { - /* maybe its proper fontconfig and we can parse it */ - pattern = FcNameParse((const FcChar8 *) fontName); - } else { - /* maybe its proper xlfd and we can convert it to an FcPattern */ - pattern = XftXlfdParse(fontName, False, False); - /*//FcPatternPrint(pattern); */ - } - - if (!pattern) - return; - - if (FcPatternGetString(pattern, FC_FAMILY, 0, &family) == FcResultMatch) - if (FcPatternGetString(pattern, FC_STYLE, 0, &style) == FcResultMatch) - if (FcPatternGetDouble(pattern, "pixelsize", 0, &size) == FcResultMatch) - setFontPanelFontName(panel, (char *)family, (char *)style, size); - - FcPatternDestroy(pattern); -} - -void WMSetFontPanelAction(WMFontPanel * panel, WMAction2 * action, void *data) -{ - panel->action = action; - panel->data = data; -} - -static void arrangeLowerFrame(FontPanel * panel) -{ - int width = WMWidgetWidth(panel->lowerF) - 55 - 30; - int height = WMWidgetHeight(panel->split) - WMWidgetHeight(panel->upperF); - int fw, tw, sw; - -#define LABEL_HEIGHT 20 - - height -= WMGetSplitViewDividerThickness(panel->split); - - height -= LABEL_HEIGHT + 8; - - fw = (125 * width) / 235; - tw = (110 * width) / 235; - sw = 55; - - WMMoveWidget(panel->famL, 10, 0); - WMResizeWidget(panel->famL, fw, LABEL_HEIGHT); - - WMMoveWidget(panel->famLs, 10, 23); - WMResizeWidget(panel->famLs, fw, height); - - WMMoveWidget(panel->typL, 10 + fw + 3, 0); - WMResizeWidget(panel->typL, tw, LABEL_HEIGHT); - - WMMoveWidget(panel->typLs, 10 + fw + 3, 23); - WMResizeWidget(panel->typLs, tw, height); - - WMMoveWidget(panel->sizL, 10 + fw + 3 + tw + 3, 0); - WMResizeWidget(panel->sizL, sw + 4, LABEL_HEIGHT); - - WMMoveWidget(panel->sizT, 10 + fw + 3 + tw + 3, 23); - WMResizeWidget(panel->sizT, sw + 4, 20); - - WMMoveWidget(panel->sizLs, 10 + fw + 3 + tw + 3, 46); - WMResizeWidget(panel->sizLs, sw + 4, height - 23); -} - -#define NUM_FIELDS 14 - -static int isXLFD(const char *font, int *length_ret) -{ - int c = 0; - - *length_ret = 0; - while (*font) { - (*length_ret)++; - if (*font++ == '-') - c++; - } - - return c == NUM_FIELDS; -} - -typedef struct { - char *typeface; - WMArray *sizes; -} Typeface; - -typedef struct { - char *name; /* gotta love simplicity */ - WMArray *typefaces; -} Family; - -static int compare_int(const void *a, const void *b) -{ - int i1 = *(int *)a; - int i2 = *(int *)b; - - if (i1 < i2) - return -1; - else if (i1 > i2) - return 1; - else - return 0; -} - -static void addSizeToTypeface(Typeface * face, int size) -{ - if (size == 0) { - int j; - - for (j = 0; j < wlengthof(scalableFontSizes); j++) { - size = scalableFontSizes[j]; - - if (!WMCountInArray(face->sizes, (void *)(uintptr_t) size)) { - WMAddToArray(face->sizes, (void *)(uintptr_t) size); - } - } - WMSortArray(face->sizes, compare_int); - } else { - if (!WMCountInArray(face->sizes, (void *)(uintptr_t) size)) { - WMAddToArray(face->sizes, (void *)(uintptr_t) size); - WMSortArray(face->sizes, compare_int); - } - } -} - -static void addTypefaceToXftFamily(Family * fam, const char *style) -{ - Typeface *face; - WMArrayIterator i; - - if (fam->typefaces) { - WM_ITERATE_ARRAY(fam->typefaces, face, i) { - if (strcmp(face->typeface, style) != 0) - continue; /* go to next interation */ - addSizeToTypeface(face, 0); - return; - } - } else { - fam->typefaces = WMCreateArray(4); - } - - face = wmalloc(sizeof(Typeface)); - - face->typeface = wstrdup(style); - face->sizes = WMCreateArray(4); - addSizeToTypeface(face, 0); - - WMAddToArray(fam->typefaces, face); -} - -/* - * families (same family name) (Hashtable of family -> array) - * registries (same family but different registries) - * - */ -static void addFontToXftFamily(WMHashTable * families, const char *name, const char *style) -{ - WMArrayIterator i; - WMArray *array; - Family *fam; - - array = WMHashGet(families, name); - if (array) { - WM_ITERATE_ARRAY(array, fam, i) { - if (strcmp(fam->name, name) == 0) - addTypefaceToXftFamily(fam, style); - return; - } - } - - array = WMCreateArray(8); - - fam = wmalloc(sizeof(Family)); - - fam->name = wstrdup(name); - - addTypefaceToXftFamily(fam, style); - - WMAddToArray(array, fam); - - WMHashInsert(families, fam->name, array); -} - -static void listFamilies(WMScreen * scr, WMFontPanel * panel) -{ - FcObjectSet *os = 0; - FcFontSet *fs; - FcPattern *pat; - WMHashTable *families; - WMHashEnumerator *enumer; - WMArray *array; - int i; - - pat = FcPatternCreate(); - os = FcObjectSetBuild(FC_FAMILY, FC_STYLE, NULL); - fs = FcFontList(0, pat, os); - if (!fs) { - WMRunAlertPanel(scr, panel->win, _("Error"), - _("Could not init font config library\n"), _("OK"), NULL, NULL); - return; - } - if (pat) - FcPatternDestroy(pat); - - families = WMCreateStringHashTable(); - - if (fs) { - for (i = 0; i < fs->nfont; i++) { - FcChar8 *family; - FcChar8 *style; - - if (FcPatternGetString(fs->fonts[i], FC_FAMILY, 0, &family) == FcResultMatch) - if (FcPatternGetString(fs->fonts[i], FC_STYLE, 0, &style) == FcResultMatch) - addFontToXftFamily(families, (char *)family, (char *)style); - } - FcFontSetDestroy(fs); - } - - enumer = WMEnumerateHashTable(families); - - while ((array = WMNextHashEnumeratorItem(enumer))) { - WMArrayIterator i; - Family *fam; - char buffer[256]; - WMListItem *item; - - WM_ITERATE_ARRAY(array, fam, i) { - strlcpy(buffer, fam->name, sizeof(buffer)); - item = WMAddListItem(panel->famLs, buffer); - - item->clientData = fam; - } - - WMFreeArray(array); - } - - WMSortListItems(panel->famLs); - - WMFreeHashTable(families); -} - -static void getSelectedFont(FontPanel * panel, char buffer[], int bufsize) -{ - WMListItem *item; - Family *family; - Typeface *face; - char *size; - - item = WMGetListSelectedItem(panel->famLs); - if (!item) - return; - family = (Family *) item->clientData; - - item = WMGetListSelectedItem(panel->typLs); - if (!item) - return; - face = (Typeface *) item->clientData; - - size = WMGetTextFieldText(panel->sizT); - - snprintf(buffer, bufsize, "%s:style=%s:pixelsize=%s", family->name, face->typeface, size); - - wfree(size); -} - -static void preview(FontPanel * panel) -{ - char buffer[512]; - WMFont *font; - - getSelectedFont(panel, buffer, sizeof(buffer)); - font = WMCreateFont(WMWidgetScreen(panel->win), buffer); - if (font) { - WMSetTextFieldFont(panel->sampleT, font); - WMReleaseFont(font); - } -} - -static void familyClick(WMWidget * w, void *data) -{ - WMList *lPtr = (WMList *) w; - WMListItem *item; - Family *family; - Typeface *face; - FontPanel *panel = (FontPanel *) data; - WMArrayIterator i; - /* current typeface and size */ - char *oface = NULL; - char *osize = NULL; - int facei = -1; - int sizei = -1; - - /* must try to keep the same typeface and size for the new family */ - item = WMGetListSelectedItem(panel->typLs); - if (item) - oface = wstrdup(item->text); - - osize = WMGetTextFieldText(panel->sizT); - - item = WMGetListSelectedItem(lPtr); - family = (Family *) item->clientData; - - WMClearList(panel->typLs); - - WM_ITERATE_ARRAY(family->typefaces, face, i) { - char buffer[256]; - int top = 0; - WMListItem *fitem; - - strlcpy(buffer, face->typeface, sizeof(buffer)); - if (strcasecmp(face->typeface, "Roman") == 0) - top = 1; - if (strcasecmp(face->typeface, "Regular") == 0) - top = 1; - if (top) - fitem = WMInsertListItem(panel->typLs, 0, buffer); - else - fitem = WMAddListItem(panel->typLs, buffer); - fitem->clientData = face; - } - - if (oface) { - facei = WMFindRowOfListItemWithTitle(panel->typLs, oface); - wfree(oface); - } - if (facei < 0) { - facei = 0; - } - WMSelectListItem(panel->typLs, facei); - typefaceClick(panel->typLs, panel); - - if (osize) { - sizei = WMFindRowOfListItemWithTitle(panel->sizLs, osize); - } - if (sizei >= 0) { - WMSelectListItem(panel->sizLs, sizei); - sizeClick(panel->sizLs, panel); - } - - if (osize) - wfree(osize); - - preview(panel); -} - -static void typefaceClick(WMWidget * w, void *data) -{ - FontPanel *panel = (FontPanel *) data; - WMListItem *item; - Typeface *face; - WMArrayIterator i; - char buffer[32]; - - char *osize = NULL; - int sizei = -1; - void *size; - - /* Parameter not used, but tell the compiler that it is ok */ - (void) w; - - osize = WMGetTextFieldText(panel->sizT); - - item = WMGetListSelectedItem(panel->typLs); - face = (Typeface *) item->clientData; - - WMClearList(panel->sizLs); - - WM_ITERATE_ARRAY(face->sizes, size, i) { - if (size != NULL) { - int size_int = (intptr_t) size; - - sprintf(buffer, "%i", size_int); - - WMAddListItem(panel->sizLs, buffer); - } - } - - if (osize) { - sizei = WMFindRowOfListItemWithTitle(panel->sizLs, osize); - } - if (sizei < 0) { - sizei = WMFindRowOfListItemWithTitle(panel->sizLs, "12"); - } - if (sizei < 0) { - sizei = 0; - } - WMSelectListItem(panel->sizLs, sizei); - WMSetListPosition(panel->sizLs, sizei); - sizeClick(panel->sizLs, panel); - - if (osize) - wfree(osize); - - preview(panel); -} - -static void sizeClick(WMWidget * w, void *data) -{ - FontPanel *panel = (FontPanel *) data; - WMListItem *item; - - /* Parameter not used, but tell the compiler that it is ok */ - (void) w; - - item = WMGetListSelectedItem(panel->sizLs); - WMSetTextFieldText(panel->sizT, item->text); - - WMSelectTextFieldRange(panel->sizT, wmkrange(0, strlen(item->text))); - - preview(panel); -} - -static void setFontPanelFontName(FontPanel * panel, const char *family, const char *style, double size) -{ - int famrow; - int stlrow; - int sz; - char asize[64]; - void *vsize; - WMListItem *item; - Family *fam; - Typeface *face; - WMArrayIterator i; - - famrow = WMFindRowOfListItemWithTitle(panel->famLs, family); - if (famrow < 0) { - famrow = 0; - return; - } - WMSelectListItem(panel->famLs, famrow); - WMSetListPosition(panel->famLs, famrow); - - WMClearList(panel->typLs); - - item = WMGetListSelectedItem(panel->famLs); - - fam = (Family *) item->clientData; - WM_ITERATE_ARRAY(fam->typefaces, face, i) { - char buffer[256]; - int top = 0; - WMListItem *fitem; - - strlcpy(buffer, face->typeface, sizeof(buffer)); - if (strcasecmp(face->typeface, "Roman") == 0) - top = 1; - if (top) - fitem = WMInsertListItem(panel->typLs, 0, buffer); - else - fitem = WMAddListItem(panel->typLs, buffer); - fitem->clientData = face; - } - - stlrow = WMFindRowOfListItemWithTitle(panel->typLs, style); - - if (stlrow < 0) { - stlrow = 0; - return; - } - - WMSelectListItem(panel->typLs, stlrow); - - item = WMGetListSelectedItem(panel->typLs); - - face = (Typeface *) item->clientData; - - WMClearList(panel->sizLs); - - WM_ITERATE_ARRAY(face->sizes, vsize, i) { - char buffer[32]; - - if (vsize != NULL) { - int size_int = (intptr_t) vsize; - - sprintf(buffer, "%i", size_int); - - WMAddListItem(panel->sizLs, buffer); - } - } - - snprintf(asize, sizeof(asize) - 1, "%d", (int)(size + 0.5)); - - sz = WMFindRowOfListItemWithTitle(panel->sizLs, asize); - - if (sz < 0) { - return; - } - - WMSelectListItem(panel->sizLs, sz); - sizeClick(panel->sizLs, panel); - - return; -} diff --git a/WINGs/wings-rs-tests/examples/font_panel.rs b/WINGs/wings-rs-tests/examples/font_panel.rs index bbcf6104..d4cc380a 100644 --- a/WINGs/wings-rs-tests/examples/font_panel.rs +++ b/WINGs/wings-rs-tests/examples/font_panel.rs @@ -1,11 +1,11 @@ -use wings_rs::WINGsP::{WMGetFontPanel, WMScreenMainLoop, WMShowFontPanel}; +use wings_rs::{font_panel::FontPanel, WINGsP::WMScreenMainLoop}; use wings_rs_tests::LiveApplication; fn main() { let app = LiveApplication::new("WMFontPanel"); + let mut font_panel = FontPanel::new(app.screen.as_ptr()).expect("could not create font panel"); + font_panel.show(); unsafe { - let font_panel = WMGetFontPanel(app.screen.as_ptr()); - WMShowFontPanel(font_panel); WMScreenMainLoop(app.screen.as_ptr()); } } diff --git a/WINGs/wings-rs-tests/tests/font_panel_tests.rs b/WINGs/wings-rs-tests/tests/font_panel_tests.rs index 0e8f0a36..675e84a3 100644 --- a/WINGs/wings-rs-tests/tests/font_panel_tests.rs +++ b/WINGs/wings-rs-tests/tests/font_panel_tests.rs @@ -1,15 +1,14 @@ -use std::time::Instant; use insta_image::assert_png_snapshot; -use wings_rs::WINGsP::{WMGetFontPanel, WMShowFontPanel}; +use std::time::Instant; +use wings_rs::font_panel::FontPanel; use wings_rs_tests::HeadlessApplication; #[test] fn show_font_panel() { let mut app = HeadlessApplication::new(); - unsafe { - let font_panel = WMGetFontPanel(app.screen.as_ptr()); - WMShowFontPanel(font_panel); - } + let mut font_panel = + FontPanel::new(app.screen.as_ptr()).expect("could not construct font panel"); + font_panel.show(); while app.pump_event_queue(Instant::now()) {} assert_png_snapshot!("font_panel", app.xvfb.png_screenshot()); } diff --git a/WINGs/wings-rs/Makefile.am b/WINGs/wings-rs/Makefile.am index aa9e1b23..ea2e386b 100644 --- a/WINGs/wings-rs/Makefile.am +++ b/WINGs/wings-rs/Makefile.am @@ -2,11 +2,15 @@ AUTOMAKE_OPTIONS = RUST_SOURCES = \ src/WINGsP.rs \ + src/button.rs \ src/configuration.rs \ src/font.rs \ + src/font_panel.rs \ src/lib.rs \ + src/list.rs \ src/pango_extras.rs \ - src/screen.rs + src/screen.rs \ + src/widget.rs RUST_EXTRA = \ Cargo.lock \ @@ -18,11 +22,44 @@ src/WINGsP.rs: ../WINGs/WINGsP.h ../../wrlib/wraster.h ../WINGs/WINGs.h ../WINGs --allowlist-type "^W_.+|^WM(View|Array|DragOperationType|Point|Data|OpenPanel|SavePanel|HashTable|DraggingInfo|SelectionProcs|Rect|EventProc|Widget|Size|Color|Pixmap|FilePanel)|R(Context|ContextAttributes|Image|RenderingMode|ScalingFilter|StdColormapMode|ImageFormat|Color)|_WINGsConfiguration" \ --allowlist-type "^WM(FontPanel|Screen|Button)" \ --allowlist-function "^WMCreateScreen|^WM(Get|Show)FontPanel|^WMCreateCommandButton|^WM(Initialize|Release)Application|^WMScreenMainLoop|^WMHandleEvent" \ + --allowlist-function "^WMWidgetScreen|^WM(Initialize|Release)Application|^WM(ScreenMainLoop|HandleEvent)|^WM(((Get|Set)TextFieldFont)|GetTextFieldText|SetTextFieldText|SelectTextFieldRange)" \ + --allowlist-type "WMList" \ + --allowlist-function "^WM(CreateList|SetListAction|GetListSelectedItem|InsertListItem|ClearList|FindRowOfListItemWithTitle|SelectListItem|SetListPosition|SortListItems)" \ + --allowlist-type "^WM(Window|Action)" \ + --allowlist-function "^WM(CreateWindow|SetWindowTitle|SetWindowMinSize|SetWindowCloseAction)" \ + --allowlist-type "^WMWidget" \ + --allowlist-function "^WM(Map|Destroy|Unmap|Resize|Realize|Move)Widget" \ + --allowlist-function "^WMWidget(Width|Height)" \ + --allowlist-function "^WM(SetWidgetBackgroundColor|SetWidgetBackgroundColor|MapSubwidgets)" \ + --allowlist-type "^WM(SplitView|SplitViewConstrainProc)" \ + --allowlist-function "^WM(SetViewNotifySizeChanges|CreateSplitView|SetSplitViewConstrainProc|GetSplitViewDividerThickness|AddSplitViewSubview)" \ + --allowlist-type "^WMNotification" \ + --allowlist-function "^WM(Add|Remove)NotificationObserver|^WMGetNotification(Object|Name)" \ + --allowlist-item "^WMNotificationObserverAction" \ + --allowlist-item "^WMViewSizeDidChangeNotification" \ + --allowlist-type "^WMFrame" \ + --allowlist-function "^WM(CreateFrame|SetFrameRelief)" \ + --allowlist-function "^WM(White|DarkGray|Release)Color" \ + --allowlist-type "^WMTextField" \ + --allowlist-function "^WM(CreateTextField)" \ + --allowlist-type "^WMLabel" \ + --allowlist-function "^WM(CreateLabel|SetLabelText|SetLabelFont|SetLabelTextColor|SetLabelRelief|SetLabelTextAlignment)" \ + --allowlist-type "^WM(Button|ButtonBehaviorMask)" \ + --allowlist-function "^WM(CreateCustomButton|SetButtonText|SetButtonAction|SetButtonText)" \ + --allowlist-function "wmkrange" \ + --allowlist-type "^WM(View|Array|DragOperationType|Point|Data|OpenPanel|SavePanel|HashTable|DraggingInfo|SelectionProcs|Rect|EventProc|Widget|Size|Color|Pixmap|FilePanel|Screen|Range|List|ListItem)" \ + --allowlist-type "^R(Context|ContextAttributes|Image|RenderingMode|ScalingFilter|StdColormapMode|ImageFormat|Color)" \ + --allowlist-type "_WINGsConfiguration" \ + --allowlist-item "^WMAlignment" \ + --allowlist-item "^WMReliefType" \ -o src/WINGsP.rs -- \ @PANGO_CFLAGS@ \ -I../../wrlib \ -I.. && ./patch_WINGsP.sh src/WINGsP.rs +Cargo.lock: + $(CARGO) build + target/debug/libwings_rs.a: $(RUST_SOURCES) $(RUST_EXTRA) $(CARGO) build diff --git a/WINGs/wings-rs/src/button.rs b/WINGs/wings-rs/src/button.rs new file mode 100644 index 00000000..0525500a --- /dev/null +++ b/WINGs/wings-rs/src/button.rs @@ -0,0 +1,22 @@ +#[allow(non_snake_case)] +pub mod ffi { + use crate::WINGsP::*; + + /// Creates a button like an "Ok" button to dismiss a dialog. + /// + /// ## Rust rewrite notes + /// + /// This was originally a macro. + #[unsafe(no_mangle)] + pub unsafe extern "C" fn WMCreateCommandButton(parent: *mut WMWidget) -> *mut WMButton { + unsafe { + WMCreateCustomButton( + parent, + (WMButtonBehaviorMask_WBBSpringLoadedMask + | WMButtonBehaviorMask_WBBPushInMask + | WMButtonBehaviorMask_WBBPushLightMask + | WMButtonBehaviorMask_WBBPushChangeMask) as i32, + ) + } + } +} diff --git a/WINGs/wings-rs/src/font_panel.rs b/WINGs/wings-rs/src/font_panel.rs new file mode 100644 index 00000000..61c8a5ef --- /dev/null +++ b/WINGs/wings-rs/src/font_panel.rs @@ -0,0 +1,1019 @@ +//! Top-level window containing a font selector. + +use crate::{ + WINGsP::*, + font::{Font, FontName}, + widget::Widget, +}; + +use std::{ + collections::{BTreeMap, btree_map::Entry}, + ffi::{CStr, CString, c_int, c_void}, + pin::Pin, + ptr::{self, NonNull}, + rc::Rc, +}; + +/// Provides quick lookup of fonts that are known to exist. +pub struct FontLibrary { + families: BTreeMap, Family>, +} + +/// Tracks a set of known [`Typeface`] objects and a quick mapping from typeface names to `Typeface`s. +#[derive(Clone)] +pub struct Family { + name: Rc, + typefaces: BTreeMap, Rc>, +} + +/// Full name information for a typeface that is known to exist on this sytem. +#[derive(Clone)] +pub struct Typeface { + family: Rc, + name: Rc, + sizes: Vec, +} + +impl FontLibrary { + /// Creates a `FontLibrary` with all fonts known by the system. + /// + /// Small errors in listing fonts (like encountering a font name which is + /// not a valid UTF-8 byte sequence) are silently ignored (and those fonts + /// are skipped). + /// + /// If an unrecoverable error is encountered while listing system fonts, + /// returns `None`. + pub fn for_all_families() -> Option { + let pattern = unsafe { fontconfig_sys::FcPatternCreate() }; + if pattern.is_null() { + // TODO: complain. + return None; + } + let object_set = unsafe { + fontconfig_sys::FcObjectSetBuild( + fontconfig_sys::constants::FC_FAMILY.as_ptr() as *mut u8, + fontconfig_sys::constants::FC_STYLE.as_ptr(), + ptr::null_mut::(), + ) + }; + if object_set.is_null() { + // TODO: complain. + unsafe { + fontconfig_sys::FcPatternDestroy(pattern); + } + return None; + } + let fonts = unsafe { fontconfig_sys::FcFontList(ptr::null_mut(), pattern, object_set) }; + unsafe { + fontconfig_sys::FcPatternDestroy(pattern); + } + if fonts.is_null() { + // TODO: complain. + return None; + } + + let mut library = FontLibrary { + families: BTreeMap::new(), + }; + if unsafe { (*fonts).nfont } < 0 { + // TODO: complain. + return None; + } + for font in + unsafe { std::slice::from_raw_parts_mut((*fonts).fonts, (*fonts).nfont as usize) } + { + let mut family = ptr::null_mut(); + let mut style = ptr::null_mut(); + unsafe { + if fontconfig_sys::FcPatternGetString( + *font, + fontconfig_sys::constants::FC_FAMILY.as_ptr(), + 0, + &mut family as *mut _, + ) == fontconfig_sys::FcResultMatch + { + let Ok(family) = CStr::from_ptr(family).to_str().map(String::from) else { + // TODO: complain. + continue; + }; + if fontconfig_sys::FcPatternGetString( + *font, + fontconfig_sys::constants::FC_STYLE.as_ptr(), + 0, + &mut style as *mut _, + ) == fontconfig_sys::FcResultMatch + { + let Ok(style) = CStr::from_ptr(style).to_str().map(String::from) else { + // TODO: complain. + continue; + }; + library.register(family, style); + } + } + } + } + unsafe { + fontconfig_sys::FcFontSetDestroy(fonts); + } + + Some(library) + } + + /// Adds a font with the given family and typeface names to the fonts tracked by `self`. + pub fn register(&mut self, family: String, typeface: String) { + let family_name: Rc = family.into(); + let typeface_name: Rc = typeface.into(); + match self.families.entry(family_name.clone()) { + Entry::Occupied(mut o) => { + if let Entry::Vacant(v) = o.get_mut().typefaces.entry(typeface_name.clone()) { + v.insert(Rc::new(Typeface { + family: family_name, + name: typeface_name, + sizes: SCALABLE_FONT_SIZES.iter().copied().collect(), + })); + } + } + Entry::Vacant(v) => { + let name = v.key().clone(); + v.insert(Family { + name, + typefaces: [( + typeface_name.clone(), + Rc::new(Typeface { + family: family_name, + name: typeface_name, + sizes: SCALABLE_FONT_SIZES.iter().copied().collect(), + }), + )] + .into_iter() + .collect(), + }); + } + } + } + + /// Searches for a font family named `family`, returning `None` if it cannot + /// be found. + pub fn get_family(&self, family: &str) -> Option { + self.families.get(family).cloned() + } + + /// Searches for a typeface with the given `family` and `style`, returning + /// `None` if it cannot be found. + pub fn get_typeface(&self, family: &str, style: &str) -> Option> { + self.families + .get(family) + .and_then(|f| f.typefaces.get(style)) + .cloned() + } +} + +pub struct FontPanel { + fonts: FontLibrary, + + window: NonNull, + + split: NonNull, + + upper_frame: NonNull, + sample_text: NonNull, + + lower_frame: NonNull, + family_label: NonNull, + family_list: NonNull, + type_label: NonNull, + type_list: NonNull, + size_label: NonNull, + size_text: NonNull, + size_list: NonNull, + + action: Option>, + + set_button: NonNull, + revert_button: NonNull, +} + +impl FontPanel { + /// Constructs a font panel that shows all fonts known to the system. + /// + /// If there are errors in listing fonts or creating widgets, returns `None`. + /// + /// Call [`FontPanel::show`] to make the panel visible. + pub fn new(screen: *mut W_Screen) -> Option>> { + extern "C" fn size_click_callback(_widget: *mut WMWidget, data: *mut c_void) { + if data.is_null() { + return; + } + let panel = unsafe { &mut *data.cast::() }; + panel.update_size_label(); + panel.update_preview(); + } + + extern "C" fn typeface_click_callback(_widget: *mut WMWidget, data: *mut c_void) { + if data.is_null() { + return; + } + + let panel = unsafe { &mut *data.cast::() }; + panel.update_typeface_label(); + panel.update_preview(); + } + + extern "C" fn family_click(_widget: *mut WMWidget, data: *mut c_void) { + if data.is_null() { + return; + } + let panel = unsafe { &mut *data.cast::() }; + panel.handle_family_click(); + panel.update_preview(); + } + + extern "C" fn close_window_callback(_widget: *mut WMWidget, data: *mut c_void) { + unsafe { + ffi::WMHideFontPanel(data.cast()); + } + } + + extern "C" fn split_view_constrain_callback( + _split_view: *mut W_SplitView, + view_index: c_int, + min: *mut c_int, + _max: *mut c_int, + ) { + unsafe { + *min = if view_index == 0 { + MIN_UPPER_HEIGHT as i32 + } else { + MIN_LOWER_HEIGHT as i32 + }; + } + } + + extern "C" fn set_clicked_action(_widget: *mut WMWidget, data: *mut c_void) { + let panel = unsafe { &mut *data.cast::() }; + if let Some(ref mut f) = panel.action { + (*f)() + } + } + + extern "C" fn revert_clicked_action(_widget: *mut WMWidget, _data: *mut c_void) {} + + let screen = NonNull::new(screen).map(|s| unsafe { &mut *s.as_ptr() })?; + let fonts = FontLibrary::for_all_families()?; + let window = NonNull::new(unsafe { WMCreateWindow(screen, c"fontPanel".as_ptr()) })?; + let split = NonNull::new(unsafe { WMCreateSplitView(window.as_ptr().cast()) })?; + let upper_frame = NonNull::new(unsafe { WMCreateFrame(window.as_ptr().cast()) })?; + let sample_text = NonNull::new(unsafe { WMCreateTextField(upper_frame.as_ptr().cast()) })?; + let lower_frame = NonNull::new(unsafe { WMCreateFrame(window.as_ptr().cast()) })?; + let family_label = NonNull::new(unsafe { WMCreateLabel(lower_frame.as_ptr().cast()) })?; + let family_list = NonNull::new(unsafe { WMCreateList(lower_frame.as_ptr().cast()) })?; + let type_label = NonNull::new(unsafe { WMCreateLabel(lower_frame.as_ptr().cast()) })?; + let type_list = NonNull::new(unsafe { WMCreateList(lower_frame.as_ptr().cast()) })?; + let size_label = NonNull::new(unsafe { WMCreateLabel(lower_frame.as_ptr().cast()) })?; + let size_text = NonNull::new(unsafe { WMCreateTextField(lower_frame.as_ptr().cast()) })?; + let size_list = NonNull::new(unsafe { WMCreateList(lower_frame.as_ptr().cast()) })?; + let set_button = NonNull::new(unsafe { + crate::button::ffi::WMCreateCommandButton(window.as_ptr().cast()) + })?; + let revert_button = NonNull::new(unsafe { + crate::button::ffi::WMCreateCommandButton(window.as_ptr().cast()) + })?; + + let mut panel = Box::new(FontPanel { + fonts, + window, + split, + upper_frame, + sample_text, + lower_frame, + family_label, + family_list, + type_label, + type_list, + size_label, + size_text, + size_list, + + action: None, + set_button, + revert_button, + }); + + unsafe { + // TODO: i18n. + WMSetWindowTitle(window.as_ptr(), c"Font Panel".as_ptr()); + WMResizeWidget(window.as_ptr().cast(), DEF_WIDTH, DEF_HEIGHT); + WMSetWindowMinSize(window.as_ptr().cast(), MIN_WIDTH, MIN_HEIGHT); + WMSetViewNotifySizeChanges((*window.as_ptr()).view().as_ptr(), 1); + WMSetWindowCloseAction( + window.as_ptr(), + Some(close_window_callback), + panel.as_mut() as *mut _ as *mut c_void, + ); + } + + unsafe { + WMResizeWidget( + split.as_ptr().cast(), + DEF_WIDTH, + DEF_HEIGHT - BUTTON_SPACE_HEIGHT, + ); + WMSetSplitViewConstrainProc(split.as_ptr(), Some(split_view_constrain_callback)); + } + + let div_width = unsafe { WMGetSplitViewDividerThickness(split.as_ptr()) }; + + unsafe { + WMSetFrameRelief(upper_frame.as_ptr(), WMReliefType_WRFlat); + WMSetViewNotifySizeChanges((*upper_frame.as_ptr()).view().as_ptr(), 1); + } + + unsafe { + WMSetFrameRelief(lower_frame.as_ptr(), WMReliefType_WRFlat); + WMSetViewNotifySizeChanges((*lower_frame.as_ptr()).view().as_ptr(), 1); + + WMAddSplitViewSubview(split.as_ptr(), (*upper_frame.as_ptr()).view().as_ptr()); + WMAddSplitViewSubview(split.as_ptr(), (*lower_frame.as_ptr()).view().as_ptr()); + + WMResizeWidget(upper_frame.as_ptr().cast(), DEF_WIDTH, DEF_UPPER_HEIGHT); + WMResizeWidget(lower_frame.as_ptr().cast(), DEF_WIDTH, DEF_LOWER_HEIGHT); + + WMMoveWidget(lower_frame.as_ptr().cast(), 0, 60 + div_width); + } + + let white = unsafe { WMWhiteColor(screen) }; + let dark = unsafe { WMDarkGrayColor(screen) }; + + unsafe { + WMResizeWidget(sample_text.as_ptr().cast(), DEF_WIDTH - 20, 50); + WMMoveWidget(sample_text.as_ptr().cast(), 10, 10); + // TODO: i18n. + WMSetTextFieldText( + sample_text.as_ptr(), + c"The quick brown fox jumps over the lazy dog".as_ptr(), + ); + } + + let font = unsafe { crate::font::ffi::WMBoldSystemFontOfSize(screen, 12) }; + + unsafe { + WMSetWidgetBackgroundColor(family_label.as_ptr().cast(), dark); + // TODO: i18n. + WMSetLabelText(family_label.as_ptr(), c"Family".as_ptr()); + WMSetLabelFont(family_label.as_ptr(), font); + WMSetLabelTextColor(family_label.as_ptr(), white); + WMSetLabelRelief(family_label.as_ptr(), WMReliefType_WRSunken); + WMSetLabelTextAlignment(family_label.as_ptr(), WMAlignment_WACenter); + } + + unsafe { + WMSetListAction( + family_list.as_ptr(), + Some(family_click), + panel.as_mut() as *mut FontPanel as *mut _, + ); + } + + unsafe { + WMSetWidgetBackgroundColor(type_label.as_ptr().cast(), dark); + // TODO: i18n. + WMSetLabelText(type_label.as_ptr(), c"Typeface".as_ptr()); + WMSetLabelFont(type_label.as_ptr(), font); + WMSetLabelTextColor(type_label.as_ptr(), white); + WMSetLabelRelief(type_label.as_ptr(), WMReliefType_WRSunken); + WMSetLabelTextAlignment(type_label.as_ptr(), WMAlignment_WACenter); + } + + unsafe { + WMSetListAction( + type_list.as_ptr(), + Some(typeface_click_callback), + panel.as_mut() as *mut FontPanel as *mut _, + ); + } + + unsafe { + WMSetWidgetBackgroundColor(size_label.as_ptr().cast(), dark); + // TODO: i18n. + WMSetLabelText(size_label.as_ptr().cast(), c"Size".as_ptr()); + WMSetLabelFont(size_label.as_ptr().cast(), font); + WMSetLabelTextColor(size_label.as_ptr().cast(), white); + WMSetLabelRelief(size_label.as_ptr().cast(), WMReliefType_WRSunken); + WMSetLabelTextAlignment(size_label.as_ptr().cast(), WMAlignment_WACenter); + } + + unsafe { + WMSetListAction( + size_list.as_ptr(), + Some(size_click_callback), + panel.as_mut() as *mut FontPanel as *mut _, + ); + } + + unsafe { + crate::font::ffi::WMReleaseFont(font); + WMReleaseColor(white); + WMReleaseColor(dark); + } + + unsafe { + WMResizeWidget(set_button.as_ptr().cast(), 70, 24); + WMMoveWidget( + set_button.as_ptr().cast(), + 240, + (DEF_HEIGHT - (BUTTON_SPACE_HEIGHT - 5)) as i32, + ); + // TODO: i18n. + WMSetButtonText(set_button.as_ptr().cast(), c"Set".as_ptr()); + WMSetButtonAction( + set_button.as_ptr().cast(), + Some(set_clicked_action), + panel.as_mut() as *mut FontPanel as *mut _, + ); + } + + unsafe { + WMResizeWidget(revert_button.as_ptr().cast(), 70, 24); + WMMoveWidget( + revert_button.as_ptr().cast(), + 80, + (DEF_HEIGHT - (BUTTON_SPACE_HEIGHT - 5)) as i32, + ); + // TODO: i18n. + WMSetButtonText(revert_button.as_ptr().cast(), c"Revert".as_ptr()); + WMSetButtonAction( + revert_button.as_ptr().cast(), + Some(revert_clicked_action), + panel.as_mut() as *mut FontPanel as *mut _, + ); + } + + unsafe { + WMRealizeWidget(window.as_ptr().cast()); + + WMMapSubwidgets(upper_frame.as_ptr().cast()); + WMMapSubwidgets(lower_frame.as_ptr().cast()); + WMMapSubwidgets(split.as_ptr().cast()); + WMMapSubwidgets(window.as_ptr().cast()); + + WMUnmapWidget(revert_button.as_ptr().cast()); + } + + panel.arrange_lower_frame(); + + extern "C" fn notification_observer(this: *mut c_void, notification: *mut WMNotification) { + if this.is_null() || notification.is_null() { + return; + } + let panel = unsafe { &mut *this.cast::() }; + let object = unsafe { WMGetNotificationObject(notification) }; + let window = (*panel).window; + + let name = unsafe { CStr::from_ptr(WMGetNotificationName(notification)) }; + if name == unsafe { CStr::from_ptr(WMViewSizeDidChangeNotification) } { + if ptr::addr_eq(object, window.as_ptr()) { + unsafe { + let h = WMWidgetHeight(window.as_ptr().cast()); + let w = WMWidgetWidth(window.as_ptr().cast()); + WMResizeWidget(panel.split.as_ptr().cast(), w, h - BUTTON_SPACE_HEIGHT); + WMMoveWidget( + panel.set_button.as_ptr().cast(), + (w - 80) as i32, + (h - (BUTTON_SPACE_HEIGHT - 5)) as i32, + ); + WMMoveWidget( + panel.revert_button.as_ptr().cast(), + (w - 240) as i32, + (h - (BUTTON_SPACE_HEIGHT - 5)) as i32, + ); + } + } else if ptr::addr_eq(object, unsafe { (*panel.upper_frame.as_ptr()).view().as_ptr() }) + { + unsafe { + if WMWidgetHeight(panel.upper_frame.as_ptr().cast()) < MIN_UPPER_HEIGHT { + WMResizeWidget( + panel.upper_frame.as_ptr().cast(), + WMWidgetWidth(panel.upper_frame.as_ptr().cast()), + MIN_UPPER_HEIGHT, + ); + } else { + WMResizeWidget( + panel.sample_text.as_ptr().cast(), + WMWidgetWidth(panel.upper_frame.as_ptr().cast()) - 20, + WMWidgetHeight(panel.upper_frame.as_ptr().cast()) - 10, + ); + } + } + } else if ptr::addr_eq(object, unsafe { (*panel.lower_frame.as_ptr()).view().as_ptr() }) + { + unsafe { + if WMWidgetHeight(panel.lower_frame.as_ptr().cast()) < MIN_LOWER_HEIGHT { + WMResizeWidget( + panel.upper_frame.as_ptr().cast(), + WMWidgetWidth(panel.upper_frame.as_ptr().cast()), + MIN_UPPER_HEIGHT, + ); + WMMoveWidget( + panel.lower_frame.as_ptr().cast(), + 0, + WMWidgetHeight(panel.upper_frame.as_ptr().cast()) as i32 + + WMGetSplitViewDividerThickness(panel.split.as_ptr()) as i32, + ); + WMResizeWidget( + panel.lower_frame.as_ptr().cast(), + WMWidgetWidth(panel.lower_frame.as_ptr().cast()) as u32, + WMWidgetWidth(panel.split.as_ptr().cast()) + - MIN_UPPER_HEIGHT as u32 + - WMGetSplitViewDividerThickness(panel.split.as_ptr().cast()) + as u32, + ); + } else { + panel.arrange_lower_frame(); + } + } + } + } + } + + unsafe { + WMAddNotificationObserver( + Some(notification_observer), + panel.as_mut() as *mut FontPanel as *mut _, + WMViewSizeDidChangeNotification, + (*window.as_ptr()).view().as_ptr().cast(), + ); + WMAddNotificationObserver( + Some(notification_observer), + panel.as_mut() as *mut FontPanel as *mut _, + WMViewSizeDidChangeNotification, + (*upper_frame.as_ptr()).view().as_ptr().cast(), + ); + WMAddNotificationObserver( + Some(notification_observer), + panel.as_mut() as *mut FontPanel as *mut _, + WMViewSizeDidChangeNotification, + (*lower_frame.as_ptr()).view().as_ptr().cast(), + ); + } + + // Add font families to the list. + for family in panel.fonts.families.values() { + let Ok(name) = CString::new(&*family.name) else { + continue; + }; + unsafe { + let item = crate::list::ffi::WMAddListItem(panel.family_list.as_ptr(), name.as_ptr()); + if !item.is_null() { + (*item).clientData = family as *const Family as *mut _; + } + } + } + unsafe { + WMSortListItems(panel.family_list.as_ptr()); + } + + Some(Pin::new(panel)) + } + + fn arrange_lower_frame(&mut self) { + const LABEL_HEIGHT: u32 = 20; + + let width = unsafe { WMWidgetWidth(self.lower_frame.as_ptr().cast()) } - 55 - 30; + let height = unsafe { + WMWidgetHeight(self.split.as_ptr().cast()) + - WMWidgetHeight(self.upper_frame.as_ptr().cast()) + - WMGetSplitViewDividerThickness(self.split.as_ptr().cast()) as u32 + - LABEL_HEIGHT + - 8 + }; + + let fw: u32 = (125 * width as u32) / 235; + let tw: u32 = (110 * width as u32) / 235; + let sw: u32 = 55; + + unsafe { + WMMoveWidget(self.family_label.as_ptr().cast(), 10, 0); + WMResizeWidget(self.family_label.as_ptr().cast(), fw, LABEL_HEIGHT); + WMMoveWidget(self.family_list.as_ptr().cast(), 10, 23); + WMResizeWidget(self.family_list.as_ptr().cast(), fw, height); + + WMMoveWidget(self.type_label.as_ptr().cast(), 10 + (fw as i32) + 3, 0); + WMResizeWidget(self.type_label.as_ptr().cast(), tw, LABEL_HEIGHT); + + WMMoveWidget(self.type_list.as_ptr().cast(), 10 + (fw as i32) + 3, 23); + WMResizeWidget(self.type_list.as_ptr().cast(), tw, height); + + WMMoveWidget( + self.size_label.as_ptr().cast(), + 10 + (fw as i32) + 3 + (tw as i32) + 3, + 0, + ); + WMResizeWidget(self.size_label.as_ptr().cast(), sw + 4, LABEL_HEIGHT); + + WMMoveWidget( + self.size_text.as_ptr().cast(), + 10 + (fw as i32) + 3 + (tw as i32) + 3, + 23, + ); + WMResizeWidget(self.size_text.as_ptr().cast(), sw + 4, 20); + + WMMoveWidget( + self.size_list.as_ptr().cast(), + 10 + (fw as i32) + 3 + (tw as i32) + 3, + 46, + ); + WMResizeWidget(self.size_list.as_ptr().cast(), sw + 4, height - 23); + } + } + + fn handle_family_click(&mut self) { + let Some(family) = (unsafe { + NonNull::new(WMGetListSelectedItem(self.family_list.as_ptr())) + .and_then(|item| NonNull::new((*item.as_ptr()).clientData.cast::())) + .map(|family| (*family.as_ptr()).clone()) + }) else { + return; + }; + // Hang onto the selected typeface and size so we can try to keep them + // selected for the new family. + let selected_type = unsafe { + NonNull::new(WMGetListSelectedItem(self.type_list.as_ptr())) + .and_then(|item| NonNull::new((*item.as_ptr()).clientData.cast::())) + .map(|typeface| (*typeface.as_ptr()).clone()) + }; + let selected_size = unsafe { + NonNull::new(WMGetListSelectedItem(self.size_list.as_ptr())) + .and_then(|item| NonNull::new((*item.as_ptr()).text)) + .map(|text| CString::from(CStr::from_ptr(text.as_ptr()))) + }; + + unsafe { + WMClearList(self.type_list.as_ptr()); + } + for face in family.typefaces.values() { + let Ok(label) = CString::new(&*face.name) else { + continue; + }; + unsafe { + let item = if face.name.eq_ignore_ascii_case("Roman") + || face.name.eq_ignore_ascii_case("Regular") + { + WMInsertListItem(self.type_list.as_ptr(), 0, label.as_ptr()) + } else { + crate::list::ffi::WMAddListItem(self.type_list.as_ptr(), label.as_ptr()) + }; + (*item).clientData = Rc::::as_ptr(face) as *mut _ + } + } + + let face_index = selected_type + .map(|x| unsafe { + WMFindRowOfListItemWithTitle(self.type_list.as_ptr(), x.name.as_ptr()) + }) + .unwrap_or(0); + + unsafe { + WMSelectListItem(self.type_list.as_ptr(), face_index); + } + let size_index = selected_size + .map(|x| unsafe { + WMFindRowOfListItemWithTitle(self.size_list.as_ptr(), x.as_ptr()) + }) + .unwrap_or(0); + unsafe { + WMSelectListItem(self.size_list.as_ptr(), size_index); + } + self.update_typeface_label(); + } + + fn update_typeface_label(&mut self) { + let selected_size_text = unsafe { + NonNull::new(WMGetTextFieldText(self.size_text.as_ptr())) + .map(|text| CString::from(CStr::from_ptr(text.as_ptr()))) + }; + + unsafe { + WMClearList(self.size_list.as_ptr() as *mut _); + } + + if let Some(selected_typeface_sizes) = + NonNull::new(unsafe { WMGetListSelectedItem(self.type_list.as_ptr()) }) + .and_then(|item| { + NonNull::new(unsafe { (*item.as_ptr()).clientData.cast::() }) + }) + .map(|typeface| unsafe { &(*typeface.as_ptr()).sizes }) + { + for size in selected_typeface_sizes { + let Ok(item_text) = CString::new(format!("{}", size)) else { + continue; + }; + unsafe { + crate::list::ffi::WMAddListItem(self.size_list.as_ptr(), item_text.as_ptr()); + } + } + } + + let mut new_size_index = 0; + if let Some(size) = selected_size_text { + new_size_index = + unsafe { WMFindRowOfListItemWithTitle(self.size_list.as_ptr(), size.as_ptr()) }; + } + if new_size_index < 0 { + new_size_index = + unsafe { WMFindRowOfListItemWithTitle(self.size_list.as_ptr(), c"12".as_ptr()) }; + } + if new_size_index < 0 { + new_size_index = 0; + } + + unsafe { + WMSelectListItem(self.size_list.as_ptr(), new_size_index); + WMSetListPosition(self.size_list.as_ptr(), new_size_index); + } + + self.update_size_label(); + } + + fn update_size_label(&mut self) { + unsafe { + let item = WMGetListSelectedItem(self.size_list.as_ptr()); + if item.is_null() { + return; + } + WMSetTextFieldText(self.size_text.as_ptr(), (*item).text); + let Ok(len) = CStr::from_ptr((*item).text).count_bytes().try_into() else { + return; + }; + WMSelectTextFieldRange(self.size_text.as_ptr(), wmkrange(0, len)); + } + } + + pub fn selected_font_name(&self) -> Option { + let typeface = unsafe { + NonNull::new(WMGetListSelectedItem(self.type_list.as_ptr())) + .and_then(|item| NonNull::new((*item.as_ptr()).clientData.cast::())) + .map(|typeface| (*typeface.as_ptr()).clone())? + }; + + let size = unsafe { + NonNull::new(WMGetTextFieldText(self.size_text.as_ptr())) + .and_then(|text| CStr::from_ptr(text.as_ptr()).to_str().ok())? + }; + + FontName::new(&format!( + "{}:style={}:pixelsize={}", + typeface.family, typeface.name, size + )) + } + + fn update_preview(&mut self) { + let Some(name) = self.selected_font_name() else { + return; + }; + let screen = unsafe { &mut *WMWidgetScreen(self.window.as_ptr().cast()) }; + let x_display = unsafe { &mut *(screen.display) }; + let x_screen = screen.screen; + let Some(mut font) = + screen.font_cache_get_or_else(name, |name| Font::load(x_display, x_screen, name)) + else { + return; + }; + unsafe { + WMSetTextFieldFont(self.sample_text.as_ptr(), &mut font as *mut _); + } + } + + pub fn set_font_name(&mut self, family: &CStr, style: &CStr, size: f64) { + let family_row = + unsafe { WMFindRowOfListItemWithTitle(self.family_list.as_ptr(), family.as_ptr()) }; + if family_row < 0 { + return; + } + unsafe { + WMSelectListItem(self.family_list.as_ptr(), family_row); + WMSetListPosition(self.family_list.as_ptr(), family_row); + WMClearList(self.type_list.as_ptr()); + } + + let item = unsafe { WMGetListSelectedItem(self.family_list.as_ptr()) }; + if let Some(family) = NonNull::new(item) + .and_then(|item| NonNull::new(unsafe { *item.as_ptr() }.clientData.cast::())) + { + for face in unsafe { (*family.as_ptr()).typefaces.values() } { + let Ok(face_name) = CString::new(&*face.name) else { + continue; + }; + let face_item = unsafe { + if face.name.eq_ignore_ascii_case("Roman") { + WMInsertListItem(self.type_list.as_ptr(), 0, face_name.as_ptr()) + } else { + crate::list::ffi::WMAddListItem(self.type_list.as_ptr(), face_name.as_ptr()) + } + }; + unsafe { + (*face_item).clientData = Rc::::as_ptr(face) as *mut _; + } + } + } + + let style_row = + unsafe { WMFindRowOfListItemWithTitle(self.type_list.as_ptr(), style.as_ptr()) }; + + if style_row < 0 { + return; + } + + unsafe { + WMSelectListItem(self.type_list.as_ptr(), style_row); + } + let item = unsafe { WMGetListSelectedItem(self.type_list.as_ptr()) }; + let face = unsafe { (*item).clientData.cast::() }; + + unsafe { + WMClearList(self.size_list.as_ptr()); + } + if !face.is_null() { + for size in unsafe { &(*face).sizes } { + if let Ok(size) = CString::new(format!("{}", size)) { + unsafe { + crate::list::ffi::WMAddListItem(self.size_list.as_ptr(), size.as_ptr()); + } + } + } + } + + if let Ok(asize) = CString::new(format!("{}", (size + 0.5) as u32)) { + let sz = + unsafe { WMFindRowOfListItemWithTitle(self.size_list.as_ptr(), asize.as_ptr()) }; + if sz < 0 { + return; + } + unsafe { + WMSelectListItem(self.size_list.as_ptr(), sz); + } + } + + self.update_size_label(); + self.update_preview(); + } + + pub fn show(&mut self) { + unsafe { + WMMapWidget(self.window.as_ptr().cast()); + } + } +} + +impl Drop for FontPanel { + fn drop(&mut self) { + unsafe { + WMRemoveNotificationObserver(self as *mut FontPanel as *mut _); + WMUnmapWidget(self.window.as_ptr().cast()); + WMDestroyWidget(self.window.as_ptr().cast()); + } + } +} + +const MIN_UPPER_HEIGHT: u32 = 20; +const MIN_LOWER_HEIGHT: u32 = 140; + +const BUTTON_SPACE_HEIGHT: u32 = 40; + +const MIN_WIDTH: u32 = 250; +const MIN_HEIGHT: u32 = MIN_UPPER_HEIGHT + MIN_LOWER_HEIGHT + BUTTON_SPACE_HEIGHT; + +const DEF_UPPER_HEIGHT: u32 = 60; +const DEF_LOWER_HEIGHT: u32 = 310; + +const DEF_WIDTH: u32 = 320; +const DEF_HEIGHT: u32 = DEF_UPPER_HEIGHT + DEF_LOWER_HEIGHT; + +const SCALABLE_FONT_SIZES: [u32; 12] = [8, 10, 11, 12, 14, 16, 18, 20, 24, 36, 48, 64]; + +pub mod ffi { + use super::FontPanel; + use crate::{ + WINGsP::*, + font::{FontName, ffi::WMFont}, + }; + + use std::{ + ffi::{CStr, c_char, c_void}, + pin::Pin, + ptr, + }; + + pub type WMFontPanel = Pin>; + + #[unsafe(no_mangle)] + pub unsafe extern "C" fn WMCreateFontPanel(screen: *mut W_Screen) -> *mut WMFontPanel { + if screen.is_null() { + return ptr::null_mut(); + } + + let screen = unsafe { &mut *screen }; + if let Some(panel) = FontPanel::new(screen) { + Box::leak(Box::new(panel)) as *mut WMFontPanel + } else { + ptr::null_mut() + } + } + + #[unsafe(no_mangle)] + pub unsafe extern "C" fn WMShowFontPanel(panel: *mut FontPanel) { + if panel.is_null() { + return; + } + let panel = unsafe { &mut *panel }; + panel.show(); + } + + #[unsafe(no_mangle)] + pub unsafe extern "C" fn WMHideFontPanel(panel: *mut FontPanel) { + unsafe { WMUnmapWidget((*panel).window.as_ptr().cast()) } + } + + #[unsafe(no_mangle)] + pub unsafe extern "C" fn WMFreeFontPanel(panel: *mut FontPanel) { + if panel.is_null() { + return; + } + let _ = unsafe { Box::from_raw(panel) }; + } + + #[unsafe(no_mangle)] + pub unsafe extern "C" fn WMSetFontPanelAction( + panel: *mut FontPanel, + action: Option, + data: *mut c_void, + ) { + if panel.is_null() { + return; + } + let panel = unsafe { &mut *panel }; + if let Some(f) = action { + let panel_ptr = panel as *mut _ as *mut c_void; + panel.action = Some(Box::new(move || unsafe { (f)(panel_ptr, data) })); + } else { + panel.action = None; + } + } + + #[unsafe(no_mangle)] + pub unsafe extern "C" fn WMSetFontPanelFont(panel: *mut FontPanel, font_name: *const c_char) { + if panel.is_null() || font_name.is_null() { + return; + } + let panel = unsafe { &mut *panel }; + let Some(name) = (unsafe { CStr::from_ptr(font_name).to_str() }) + .ok() + .and_then(|n| FontName::new(n)) + else { + return; + }; + + unsafe { + let pattern = fontconfig_sys::FcNameParse(name.as_xft_name()); + if pattern.is_null() { + return; + } + let mut family = ptr::null_mut(); + let mut style = ptr::null_mut(); + let mut size = 0.0; + if fontconfig_sys::FcPatternGetString( + pattern, + fontconfig_sys::constants::FC_FAMILY.as_ptr(), + 0, + &mut family, + ) == fontconfig_sys::FcResultMatch + { + if fontconfig_sys::FcPatternGetString( + pattern, + fontconfig_sys::constants::FC_STYLE.as_ptr(), + 0, + &mut style, + ) == fontconfig_sys::FcResultMatch + { + if fontconfig_sys::FcPatternGetDouble( + pattern, + c"pixelsize".as_ptr(), + 0, + &mut size, + ) == fontconfig_sys::FcResultMatch + { + panel.set_font_name(CStr::from_ptr(family), CStr::from_ptr(style), size); + } + } + } + fontconfig_sys::FcPatternDestroy(pattern); + } + } + + #[unsafe(no_mangle)] + pub unsafe extern "C" fn WMGetFontPanelFont(panel: *mut FontPanel) -> *mut WMFont { + if panel.is_null() { + return ptr::null_mut(); + } + return unsafe { WMGetTextFieldFont((*panel).sample_text.as_ptr()) }; + } +} diff --git a/WINGs/wings-rs/src/lib.rs b/WINGs/wings-rs/src/lib.rs index 31ba6e97..02464da9 100644 --- a/WINGs/wings-rs/src/lib.rs +++ b/WINGs/wings-rs/src/lib.rs @@ -3,7 +3,11 @@ #[allow(non_snake_case)] #[allow(non_upper_case_globals)] pub mod WINGsP; +pub mod button; pub mod configuration; pub mod font; +pub mod font_panel; +pub mod list; pub(crate) mod pango_extras; pub mod screen; +pub mod widget; diff --git a/WINGs/wings-rs/src/list.rs b/WINGs/wings-rs/src/list.rs new file mode 100644 index 00000000..dcfbaf34 --- /dev/null +++ b/WINGs/wings-rs/src/list.rs @@ -0,0 +1,15 @@ +#[allow(non_snake_case)] +pub mod ffi { + use crate::WINGsP::*; + + use std::ffi::c_char; + + /// Appends an item with the label `text` to `list`. + /// + /// ## Rust rewrite notes + /// + /// This was originally a macro. + pub unsafe fn WMAddListItem(list: *mut WMList, text: *const c_char) -> *mut WMListItem { + unsafe { WMInsertListItem(list, -1, text) } + } +} diff --git a/WINGs/wings-rs/src/widget.rs b/WINGs/wings-rs/src/widget.rs new file mode 100644 index 00000000..27ec7bff --- /dev/null +++ b/WINGs/wings-rs/src/widget.rs @@ -0,0 +1,57 @@ +//! Common WINGs widget fields. +//! +//! WINGs widgets all have a [`WINGsP::WMView`] object that tracks common attributes +//! like size and location and a [`WINGsP::W_Class`] field that identifies the widget +//! type (button, list, etc.). This module provides access to these fields. +//! +//! ## Rust rewrite notes +//! +//! The original WINGs implementation uses casting and a common layout for +//! "primitive" widget structs, such that the first two fields are always a +//! `W_Class` and a `*mut WMView`. We rely on this convention when implementing +//! [`Widget`] for widgets that are defined in C. +use crate::WINGsP; +use std::{ + ptr::NonNull, +}; + +/// Base WINGs widget type. +/// +/// All primitive widgets (like buttons and lists) should implement this. +pub trait Widget { + fn class(&self) -> WINGsP::W_Class; + fn view(&self) -> NonNull; +} + +/// Common widget header fields. It should be safe to cast opaque WINGs widget +/// structs defined in C to this type to access these fields. Before writing +/// code that casts anything to this, double-check the widget definition! +#[repr(C)] +struct WidgetLike { + class: WINGsP::W_Class, + view: *mut WINGsP::WMView, +} + +/// Casts `$widget` to [`WidgetLike`]. Provided for convenience. +/// +/// ## Safety +/// +/// The caller of this macro is responsible for ensuring that the layout of `$widget` is compatible +/// with `WidgetLike`. +macro_rules! impl_widget { + ($widget:ty) => { + impl Widget for $widget { + fn class(&self) -> crate::WINGsP::W_Class { + unsafe { ::std::mem::transmute::<&$widget, &WidgetLike>(self).class } + } + + fn view(&self) -> ::std::ptr::NonNull { + let widget = unsafe { ::std::mem::transmute::<&$widget, &WidgetLike>(self) }; + NonNull::new(widget.view).expect("WMView field of widget struct cannot be null") + } + } + }; +} + +impl_widget!(WINGsP::WMWindow); +impl_widget!(WINGsP::WMFrame);