mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-01 06:35:42 +00:00
1636 lines
44 KiB
C
1636 lines
44 KiB
C
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
|
|
*
|
|
* The contents of this file are subject to the Netscape Public License
|
|
* Version 1.0 (the "NPL"); you may not use this file except in
|
|
* compliance with the NPL. You may obtain a copy of the NPL at
|
|
* http://www.mozilla.org/NPL/
|
|
*
|
|
* Software distributed under the NPL is distributed on an "AS IS" basis,
|
|
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the NPL
|
|
* for the specific language governing rights and limitations under the
|
|
* NPL.
|
|
*
|
|
* The Initial Developer of this code under the NPL is Netscape
|
|
* Communications Corporation. Portions created by Netscape are
|
|
* Copyright (C) 1998 Netscape Communications Corporation. All Rights
|
|
* Reserved.
|
|
*/
|
|
/*
|
|
outline.c --- the outline widget hack.
|
|
Created: Terry Weissman <terry@netscape.com>, 24-Jun-95.
|
|
*/
|
|
|
|
|
|
#include "mozilla.h"
|
|
#include "xfe.h"
|
|
#include <msgcom.h>
|
|
|
|
#include "outline.h"
|
|
#include "dragdrop.h"
|
|
|
|
#include <Xfe/Xfe.h> /* for xfe widgets and utilities */
|
|
|
|
#ifndef _STDC_
|
|
#define _STDC_ 1
|
|
#define HACKED_STDC 1
|
|
#endif
|
|
|
|
#define XmLBACKWARDS_COMPATIBILITY
|
|
|
|
#include <XmL/GridP.h>
|
|
#include <Xm/Label.h> /* For the drag window pixmap label */
|
|
|
|
#ifdef HACKED_STDC
|
|
#undef HACKED_STDC
|
|
#undef _STDC_
|
|
#endif
|
|
|
|
#include "icons.h"
|
|
#include "icondata.h"
|
|
|
|
#ifdef FREEIF
|
|
#undef FREEIF
|
|
#endif
|
|
#define FREEIF(obj) do { if (obj) { XP_FREE (obj); obj = 0; }} while (0)
|
|
|
|
#define PIXMAP_WIDTH 18 /* #### */
|
|
#define PIXMAP_INDENT 10
|
|
|
|
#define LINE_AREA_WIDTH 7
|
|
|
|
#define FLIPPY_WIDTH 14 /* #### */
|
|
|
|
#define MIN_COLUMN_WIDTH 10 /* So that the user can manipulate it. */
|
|
#define MAX_COLUMN_WIDTH 10000 /* So that we don't overflow any 16-bit
|
|
numbers. */
|
|
|
|
static Widget DataWidget = NULL;
|
|
|
|
typedef struct fe_OutlineInfo {
|
|
Widget widget;
|
|
Widget drag_widget;
|
|
int numcolumns;
|
|
int numrows;
|
|
fe_OutlineGetDataFunc datafunc;
|
|
fe_OutlineClickFunc clickfunc;
|
|
fe_OutlineIconClickFunc iconfunc;
|
|
MSG_Pane *pane;
|
|
void* closure;
|
|
|
|
int lastx;
|
|
int lasty;
|
|
Time lastdowntime;
|
|
Time lastuptime;
|
|
XP_Bool ignoreevents;
|
|
EventMask activity;
|
|
|
|
/* indented column specific stuff. */
|
|
int *column_indented; /* the columns that are to be indented. */
|
|
int indent_amount; /* the amount to indent each subline. */
|
|
|
|
/* drag specific stuff. */
|
|
XP_Bool drag_enabled;
|
|
fe_icon *drag_icon;
|
|
void* dragscrolltimer;
|
|
int dragscrolldir;
|
|
int dragrow;
|
|
XP_Bool dragrowbox;
|
|
GC draggc;
|
|
fe_dnd_Type drag_type;
|
|
|
|
XP_Bool allowiconresize;
|
|
int iconcolumnwidth;
|
|
int* columnwidths;
|
|
int* columnIndex; /* Which column to display where. If
|
|
columnIndex[1] == 3, then display the third
|
|
column's data in column 1. Column zero is
|
|
the icon column.*/
|
|
char** posinfo; /* Pointer to a string where we keep the
|
|
current column ordering and widths, in the
|
|
form to be saved in the preferences file.*/
|
|
int dragcolumn;
|
|
Pixmap dragColumnPixmap;
|
|
unsigned char clickrowtype;
|
|
const char** headers; /* What headers to display on the columns.
|
|
Entry zero is for the icon column. */
|
|
XP_Bool* headerhighlight;
|
|
|
|
void* visibleTimer;
|
|
int visibleLine;
|
|
|
|
fe_OutlineDesc Data;
|
|
int DataRow;
|
|
} fe_OutlineInfo;
|
|
|
|
|
|
static fe_icon pixmapFlippyFolded = { 0 };
|
|
static fe_icon pixmapFlippyExpanded = { 0 };
|
|
|
|
#if 0
|
|
static fe_icon pixmapMarked = { 0 };
|
|
static fe_icon pixmapUnmarked = { 0 };
|
|
#endif
|
|
|
|
static fe_dnd_Source dragsource = {0};
|
|
|
|
/* static variables to make the conversion from our style
|
|
to fontlist tags easier (and less of a memory problem.) */
|
|
|
|
static char *ITALIC_STYLE = "ITALIC";
|
|
static char *BOLD_STYLE = "BOLD";
|
|
static char *DEFAULT_STYLE = XmFONTLIST_DEFAULT_TAG; /* Needs to be this for i18n to work. */
|
|
|
|
static char *
|
|
fe_outline_style_to_tag(fe_OutlineTextStyle style)
|
|
{
|
|
switch (style)
|
|
{
|
|
case FE_OUTLINE_Italic:
|
|
return ITALIC_STYLE;
|
|
case FE_OUTLINE_Bold:
|
|
return BOLD_STYLE;
|
|
case FE_OUTLINE_Default:
|
|
return DEFAULT_STYLE;
|
|
default:
|
|
XP_ASSERT(0);
|
|
return DEFAULT_STYLE;
|
|
}
|
|
}
|
|
|
|
static void
|
|
fe_make_outline_drag_widget (fe_OutlineInfo *info,
|
|
fe_OutlineDesc *data, int x, int y)
|
|
{
|
|
#if 0
|
|
Display *dpy = XtDisplay (info->widget);
|
|
XmString str;
|
|
int str_w, str_h;
|
|
#endif
|
|
Visual *v = 0;
|
|
Colormap cmap = 0;
|
|
Cardinal depth = 0;
|
|
Widget label;
|
|
XmFontList fontList;
|
|
/*fe_icon *icon;*/
|
|
Widget shell;
|
|
Pixmap dragPixmap;
|
|
|
|
if (dragsource.widget) return;
|
|
|
|
shell = info->widget;
|
|
while (XtParent(shell) && !XtIsShell(shell)) {
|
|
shell = XtParent(shell);
|
|
}
|
|
|
|
XtVaGetValues (shell, XtNvisual, &v, XtNcolormap, &cmap,
|
|
XtNdepth, &depth, 0);
|
|
|
|
XtVaGetValues (info->widget, XmNfontList, &fontList, NULL);
|
|
|
|
/* ### free the string
|
|
str = XmStringCreateLocalized(data->str[5]);
|
|
str_w = XmStringWidth (fontList, str);
|
|
str_h = XmStringHeight (fontList, str);
|
|
*/
|
|
|
|
dragsource.type = info->clickrowtype == XmCONTENT ? info->drag_type : FE_DND_COLUMN;
|
|
dragsource.closure = info->widget;
|
|
|
|
if (info->dragColumnPixmap)
|
|
{
|
|
XFreePixmap(XtDisplay(info->widget), info->dragColumnPixmap);
|
|
info->dragColumnPixmap = 0;
|
|
}
|
|
|
|
if (dragsource.type == FE_DND_COLUMN) {
|
|
XRectangle cellrect;
|
|
XGCValues gcv;
|
|
|
|
XmLGridRowColumnToXY(info->widget,
|
|
XmHEADING, 0,
|
|
XmCONTENT, info->dragcolumn,
|
|
True, &cellrect);
|
|
|
|
/* if the column extends past the right hand side of the widget, clip
|
|
it. */
|
|
{
|
|
Dimension clipwidth;
|
|
Widget vertscrollbar;
|
|
|
|
XtVaGetValues(info->widget,
|
|
XmNverticalScrollBar, &vertscrollbar,
|
|
NULL);
|
|
|
|
clipwidth = XfeWidth(info->widget);
|
|
|
|
if (vertscrollbar && XtIsManaged(vertscrollbar))
|
|
clipwidth -= XfeWidth(vertscrollbar);
|
|
|
|
if (cellrect.x + cellrect.width > (int)clipwidth)
|
|
cellrect.width = clipwidth - cellrect.x;
|
|
}
|
|
|
|
#ifndef DONT_DRAG_COLUMNS
|
|
cellrect.height = XfeHeight(info->widget) - cellrect.y;
|
|
#endif
|
|
|
|
if (!info->draggc)
|
|
info->draggc = XCreateGC(XtDisplay(info->widget),
|
|
XtWindow(info->widget),
|
|
0, &gcv);
|
|
|
|
dragPixmap = info->dragColumnPixmap = XCreatePixmap(XtDisplay(info->widget),
|
|
XtWindow(info->widget),
|
|
cellrect.width,
|
|
cellrect.height,
|
|
depth);
|
|
|
|
XCopyArea(XtDisplay(info->widget), XtWindow(info->widget),
|
|
info->dragColumnPixmap, info->draggc,
|
|
cellrect.x, cellrect.y, cellrect.width, cellrect.height,
|
|
0,0);
|
|
|
|
dragsource.widget = XtVaCreateWidget ("drag_win",
|
|
overrideShellWidgetClass,
|
|
info->widget,
|
|
XmNwidth, cellrect.width,
|
|
XmNheight, cellrect.height,
|
|
XmNvisual, v,
|
|
XmNcolormap, cmap,
|
|
XmNdepth, depth,
|
|
XmNborderWidth, 0,
|
|
NULL);
|
|
} else {
|
|
|
|
XP_ASSERT(info->drag_icon != NULL);
|
|
if (info->drag_icon == NULL) return;
|
|
|
|
dragPixmap = info->drag_icon->pixmap;
|
|
|
|
dragsource.widget = XtVaCreateWidget ("drag_win",
|
|
overrideShellWidgetClass,
|
|
info->widget,
|
|
XmNwidth, info->drag_icon->width,
|
|
XmNheight, info->drag_icon->height,
|
|
XmNvisual, v,
|
|
XmNcolormap, cmap,
|
|
XmNdepth, depth,
|
|
XmNborderWidth, 0,
|
|
NULL);
|
|
}
|
|
|
|
|
|
label = XtVaCreateManagedWidget ("label",
|
|
xmLabelWidgetClass,
|
|
dragsource.widget,
|
|
XmNlabelType, XmPIXMAP,
|
|
XmNlabelPixmap, dragPixmap,
|
|
NULL);
|
|
|
|
/* XmStringFree(str); */
|
|
}
|
|
|
|
|
|
static void
|
|
fe_destroy_outline_drag_widget (void)
|
|
{
|
|
if (!dragsource.widget) return;
|
|
XtDestroyWidget (dragsource.widget);
|
|
dragsource.widget = NULL;
|
|
}
|
|
|
|
static fe_OutlineInfo*
|
|
fe_get_info(Widget outline)
|
|
{
|
|
fe_OutlineInfo* result = NULL;
|
|
XtVaGetValues(outline, XmNuserData, &result, 0);
|
|
assert(result->widget == outline);
|
|
return result;
|
|
}
|
|
|
|
|
|
static void
|
|
PixmapDraw(Widget w, Pixmap pixmap, Pixmap mask,
|
|
int pixmapWidth, int pixmapHeight, unsigned char alignment, GC gc,
|
|
XRectangle *rect, XRectangle *clipRect)
|
|
{
|
|
Display *dpy;
|
|
Window win;
|
|
int needsClip;
|
|
int x, y, width, height;
|
|
|
|
if (pixmap == XmUNSPECIFIED_PIXMAP) return;
|
|
if (rect->width <= 4 || rect->height <= 4) return;
|
|
if (clipRect->width < 3 || clipRect->height < 3) return;
|
|
if (!XtIsRealized(w)) return;
|
|
dpy = XtDisplay(w);
|
|
win = XtWindow(w);
|
|
|
|
width = pixmapWidth;
|
|
height = pixmapHeight;
|
|
if (!width || !height) {
|
|
alignment = XmALIGNMENT_TOP_LEFT;
|
|
width = clipRect->width - 4;
|
|
height = clipRect->height - 4;
|
|
}
|
|
|
|
if (alignment == XmALIGNMENT_TOP_LEFT ||
|
|
alignment == XmALIGNMENT_LEFT ||
|
|
alignment == XmALIGNMENT_BOTTOM_LEFT) {
|
|
x = rect->x + 2;
|
|
} else if (alignment == XmALIGNMENT_TOP ||
|
|
alignment == XmALIGNMENT_CENTER ||
|
|
alignment == XmALIGNMENT_BOTTOM) {
|
|
x = rect->x + ((int)rect->width - width) / 2;
|
|
} else {
|
|
x = rect->x + rect->width - width - 2;
|
|
}
|
|
|
|
if (alignment == XmALIGNMENT_TOP ||
|
|
alignment == XmALIGNMENT_TOP_LEFT ||
|
|
alignment == XmALIGNMENT_TOP_RIGHT) {
|
|
y = rect->y + 2;
|
|
} else if (alignment == XmALIGNMENT_LEFT ||
|
|
alignment == XmALIGNMENT_CENTER ||
|
|
alignment == XmALIGNMENT_RIGHT) {
|
|
y = rect->y + ((int)rect->height - height) / 2;
|
|
} else {
|
|
y = rect->y + rect->height - height - 2;
|
|
}
|
|
|
|
needsClip = 1;
|
|
if (clipRect->x <= x &&
|
|
clipRect->y <= y &&
|
|
clipRect->x + clipRect->width >= x + width &&
|
|
clipRect->y + clipRect->height >= y + height) {
|
|
needsClip = 0;
|
|
}
|
|
|
|
if (needsClip) {
|
|
XSetClipRectangles(dpy, gc, 0, 0, clipRect, 1, Unsorted);
|
|
} else if (mask) {
|
|
XSetClipMask(dpy, gc, mask);
|
|
XSetClipOrigin(dpy, gc, x, y);
|
|
}
|
|
XSetGraphicsExposures(dpy, gc, False);
|
|
XCopyArea(dpy, pixmap, win, gc, 0, 0, width, height, x, y);
|
|
XSetGraphicsExposures(dpy, gc, True);
|
|
if (needsClip || mask) {
|
|
XSetClipMask(dpy, gc, None);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
extern XmString fe_StringChopCreate(char* message, char* tag,
|
|
XmFontList font_list, int maxwidth);
|
|
|
|
static void
|
|
fe_outline_celldraw(Widget widget, XtPointer clientData, XtPointer callData)
|
|
{
|
|
fe_OutlineInfo* info = (fe_OutlineInfo*) clientData;
|
|
XmLGridCallbackStruct* call = (XmLGridCallbackStruct*) callData;
|
|
XmLGridDrawStruct *draw = call->drawInfo;
|
|
Boolean drawSelected = draw->drawSelected;
|
|
XmLCGridRow *row;
|
|
XmLCGridColumn *column;
|
|
unsigned char alignment;
|
|
Pixel fg;
|
|
XmFontList fontList;
|
|
XmString str = NULL;
|
|
const char* ptr;
|
|
fe_icon* data = NULL;
|
|
XRectangle r, textRect;
|
|
int sourcecol;
|
|
|
|
if (call->rowType != XmCONTENT) return;
|
|
|
|
row = XmLGridGetRow(widget, call->rowType, call->row);
|
|
column = XmLGridGetColumn(widget, call->columnType, call->column);
|
|
|
|
/* fill in useful things into the data sent to the datafunc. */
|
|
info->Data.column_headers = (const char **)info->headers;
|
|
info->Data.numcolumns = info->numcolumns;
|
|
info->Data.selected = (XP_Bool)drawSelected;
|
|
|
|
if (DataWidget != widget || call->row != info->DataRow) {
|
|
if (!(*info->datafunc)(widget, info->closure, call->row, &info->Data,
|
|
0)) {
|
|
info->DataRow = -1;
|
|
return;
|
|
}
|
|
DataWidget = widget;
|
|
info->DataRow = call->row;
|
|
}
|
|
|
|
sourcecol = info->columnIndex[call->column];
|
|
|
|
if (sourcecol == 0)
|
|
{
|
|
/* Draw the flippy, if any. */
|
|
if (info->Data.flippy != FE_OUTLINE_Leaf)
|
|
{
|
|
switch (info->Data.flippy) {
|
|
case FE_OUTLINE_Folded: data = &pixmapFlippyFolded; break;
|
|
case FE_OUTLINE_Expanded: data = &pixmapFlippyExpanded; break;
|
|
default:
|
|
XP_ASSERT(0);
|
|
}
|
|
r = *draw->cellRect;
|
|
r.width = data->width;
|
|
PixmapDraw(widget, data->pixmap, data->mask, data->width, data->height,
|
|
XmALIGNMENT_LEFT, draw->gc, &r, call->clipRect);
|
|
}
|
|
|
|
#if 0
|
|
/* Draw the indented icon */
|
|
r = *draw->cellRect;
|
|
data = fe_outline_lookup_icon(Data.icon);
|
|
XP_ASSERT(data);
|
|
if (!data) return;
|
|
|
|
r.x += Data.depth * PIXMAP_INDENT + FLIPPY_WIDTH;
|
|
r.width = data->width;
|
|
if (r.x + r.width > draw->cellRect->x + draw->cellRect->width) {
|
|
char buf[10];
|
|
XRectangle rect;
|
|
rect = *draw->cellRect;
|
|
rect.width -= r.width;
|
|
PR_snprintf(buf, sizeof (buf), "%d", Data.depth);
|
|
XtVaGetValues(widget,
|
|
XmNrowPtr, row,
|
|
XmNcolumnPtr, column,
|
|
XmNcellForeground, &fg,
|
|
XmNcellFontList, &fontList,
|
|
NULL);
|
|
str = XmStringCreate(buf, DEFAULT_STYLE);
|
|
XSetForeground(XtDisplay(widget), draw->gc,
|
|
drawSelected ? draw->selectForeground : fg);
|
|
XmLStringDraw(widget, str, draw->stringDirection, fontList,
|
|
XmALIGNMENT_RIGHT, draw->gc, &rect, &rect);
|
|
r.x = draw->cellRect->x + draw->cellRect->width - r.width;
|
|
}
|
|
PixmapDraw(widget, data->pixmap, data->mask, data->width, data->height,
|
|
XmALIGNMENT_LEFT, draw->gc, &r, call->clipRect);
|
|
#endif
|
|
|
|
}
|
|
else /* sourcecol != 0 */
|
|
{
|
|
ptr = info->Data.str[sourcecol - 1];
|
|
|
|
/* if we're the indented column, we push everything over by the indented
|
|
amount -- the depth * the indent_amount */
|
|
if (info->column_indented[call->column] & FE_OUTLINE_IndentedColumn)
|
|
{
|
|
Dimension indent_amount = info->Data.depth * info->indent_amount;
|
|
|
|
draw->cellRect->x += indent_amount;
|
|
draw->cellRect->width -= indent_amount;
|
|
}
|
|
|
|
/* if there's an icon, we need to push the text over. */
|
|
if (info->Data.type[sourcecol - 1] & FE_OUTLINE_Icon)
|
|
{
|
|
PixmapDraw(widget, info->Data.icons[sourcecol - 1]->pixmap,
|
|
info->Data.icons[sourcecol - 1]->mask,
|
|
info->Data.icons[sourcecol - 1]->width,
|
|
info->Data.icons[sourcecol - 1]->height,
|
|
XmALIGNMENT_LEFT, draw->gc, draw->cellRect,
|
|
call->clipRect);
|
|
|
|
textRect.x = draw->cellRect->x + info->Data.icons[sourcecol - 1]->width + 4;
|
|
textRect.y = draw->cellRect->y;
|
|
textRect.width = draw->cellRect->width - info->Data.icons[sourcecol - 1]->height - 4;
|
|
textRect.height = draw->cellRect->height;
|
|
}
|
|
else
|
|
{
|
|
textRect = *draw->cellRect;
|
|
}
|
|
|
|
if (info->Data.type[sourcecol - 1] & FE_OUTLINE_String)
|
|
{
|
|
XtVaGetValues(widget,
|
|
XmNrowPtr, row,
|
|
XmNcolumnPtr, column,
|
|
XmNcellForeground, &fg,
|
|
XmNcellAlignment, &alignment,
|
|
XmNcellFontList, &fontList,
|
|
NULL);
|
|
if (call->clipRect->width > 4)
|
|
{
|
|
/* Impose some spacing between columns. What a hack. ### */
|
|
call->clipRect->width -= 4;
|
|
|
|
if (info->Data.type[sourcecol - 1] == FE_OUTLINE_ChoppedString)
|
|
{
|
|
str = fe_StringChopCreate((char*) ptr, fe_outline_style_to_tag(info->Data.style),
|
|
fontList,
|
|
call->clipRect->width);
|
|
}
|
|
else
|
|
{
|
|
str = XmStringCreate((char *) ptr, fe_outline_style_to_tag(info->Data.style));
|
|
}
|
|
|
|
XSetForeground(XtDisplay(widget), draw->gc,
|
|
drawSelected ? draw->selectForeground : fg);
|
|
XmLStringDraw(widget, str, draw->stringDirection, fontList, alignment,
|
|
draw->gc, &textRect, call->clipRect);
|
|
call->clipRect->width += 4;
|
|
}
|
|
}
|
|
}
|
|
if (call->row == info->dragrow && sourcecol > 0)
|
|
{
|
|
int y;
|
|
int x2 = draw->cellRect->x + draw->cellRect->width - 1;
|
|
XP_Bool rightedge = FALSE;
|
|
if (call->column == info->numcolumns)
|
|
{
|
|
rightedge = TRUE;
|
|
if (str)
|
|
{
|
|
int xx = draw->cellRect->x + XmStringWidth(fontList, str) + 4;
|
|
if (x2 > xx) x2 = xx;
|
|
}
|
|
}
|
|
if (info->draggc == NULL)
|
|
{
|
|
XGCValues gcv;
|
|
#if 0
|
|
Pixel foreground;
|
|
#endif
|
|
gcv.foreground = fg;
|
|
info->draggc = XCreateGC(XtDisplay(widget), XtWindow(widget),
|
|
GCForeground, &gcv);
|
|
}
|
|
y = draw->cellRect->y + draw->cellRect->height - 1;
|
|
XDrawLine(XtDisplay(widget), XtWindow(widget), info->draggc,
|
|
draw->cellRect->x, y, x2, y);
|
|
if (info->dragrowbox)
|
|
{
|
|
int y0 = draw->cellRect->y;
|
|
if (call->column == 1)
|
|
{
|
|
XDrawLine(XtDisplay(widget), XtWindow(widget), info->draggc,
|
|
draw->cellRect->x, y0, draw->cellRect->x, y);
|
|
}
|
|
if (rightedge)
|
|
{
|
|
XDrawLine(XtDisplay(widget), XtWindow(widget), info->draggc,
|
|
x2, y0, x2, y);
|
|
}
|
|
XDrawLine(XtDisplay(widget), XtWindow(widget), info->draggc,
|
|
draw->cellRect->x, y0, x2, y0);
|
|
|
|
}
|
|
}
|
|
if (str) XmStringFree(str);
|
|
}
|
|
|
|
|
|
static void
|
|
fe_outline_click(Widget widget, XtPointer clientData, XtPointer callData)
|
|
{
|
|
fe_OutlineInfo* info = (fe_OutlineInfo*) clientData;
|
|
XmLGridCallbackStruct* call = (XmLGridCallbackStruct*) callData;
|
|
XEvent* event;
|
|
int row;
|
|
unsigned int state;
|
|
int sourcecol = info->columnIndex[call->column];
|
|
|
|
event = call->event;
|
|
if (!event)
|
|
state = 0;
|
|
else if (event->type == ButtonPress || event->type == ButtonRelease)
|
|
state = event->xbutton.state;
|
|
else if (event->type == KeyPress || event->type == KeyRelease)
|
|
state = event->xkey.state;
|
|
else
|
|
state = 0;
|
|
|
|
if (call->rowType == XmHEADING)
|
|
row = -1;
|
|
else
|
|
row = call->row;
|
|
(*info->clickfunc)(widget, info->closure, row,
|
|
sourcecol - 1,
|
|
info->headers ? info->headers[sourcecol] : NULL,
|
|
1, call->reason == XmCR_ACTIVATE ? 2 : 1,
|
|
(state & ShiftMask) != 0, (state & ControlMask) != 0,
|
|
0);
|
|
}
|
|
|
|
|
|
static int last_motion_x = 0;
|
|
static int last_motion_y = 0;
|
|
|
|
static void
|
|
UpdateData (Widget widget, fe_OutlineInfo *info, int row)
|
|
{
|
|
if (widget != DataWidget || row != info->DataRow) {
|
|
if (!(*info->datafunc)(widget, info->closure, row, &info->Data, 0)) {
|
|
return;
|
|
}
|
|
DataWidget = widget;
|
|
info->DataRow = row;
|
|
}
|
|
}
|
|
|
|
static XP_Bool
|
|
RowIsSelected(fe_OutlineInfo* info, int row)
|
|
{
|
|
int* position;
|
|
int n = XmLGridGetSelectedRowCount(info->widget);
|
|
XP_Bool result = FALSE;
|
|
int i;
|
|
if (n > 0) {
|
|
position = XP_ALLOC(sizeof(int) * n);
|
|
if (position) {
|
|
XmLGridGetSelectedRows(info->widget, position, n);
|
|
for (i=0 ; i<n ; i++) {
|
|
if (position[i] == row) {
|
|
result = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
XP_FREE(position);
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
|
|
static void
|
|
fe_outline_visible_timer(void* closure)
|
|
{
|
|
fe_OutlineInfo* info = (fe_OutlineInfo*) closure;
|
|
info->visibleTimer = NULL;
|
|
if (info->visibleLine >= 0) {
|
|
/* First and check that the user isn't still busy with his mouse. If
|
|
he is, then we'll do this stuff when activity goes to 0. */
|
|
if (info->activity == 0) {
|
|
fe_OutlineMakeVisible(info->widget, info->visibleLine);
|
|
info->visibleLine = -1;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
static void
|
|
SendClick(fe_OutlineInfo* info, XEvent* event, XP_Bool only_if_not_selected)
|
|
{
|
|
int x = event->xbutton.x;
|
|
int y = event->xbutton.y;
|
|
int numclicks = 1;
|
|
unsigned char rowtype;
|
|
unsigned char coltype;
|
|
int row;
|
|
int column;
|
|
unsigned int state = 0;
|
|
|
|
if (!only_if_not_selected &&
|
|
abs(x - info->lastx) < 5 && abs(y - info->lasty) < 5 &&
|
|
(info->lastdowntime - info->lastuptime <=
|
|
XtGetMultiClickTime(XtDisplay(info->widget)))) {
|
|
numclicks = 2; /* ### Allow triple clicks? */
|
|
}
|
|
info->lastx = x;
|
|
info->lasty = y;
|
|
|
|
if (XmLGridXYToRowColumn(info->widget, x, y,
|
|
&rowtype, &row, &coltype, &column) >= 0) {
|
|
if (rowtype == XmHEADING) row = -1;
|
|
if (coltype == XmCONTENT) {
|
|
info->activity = ButtonPressMask;
|
|
info->ignoreevents = True;
|
|
if (column < 1 && !only_if_not_selected && row >= 0) {
|
|
UpdateData(info->widget, info, row);
|
|
if (1 /*### Pixel compare the click with where we drew icon*/) {
|
|
if (numclicks == 1) {
|
|
(*info->iconfunc)(info->widget, info->closure, row, 0);
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
state = event->xbutton.state;
|
|
if (!only_if_not_selected || !RowIsSelected(info, row)) {
|
|
int sourcecol = info->columnIndex[column];
|
|
if (numclicks == 1) {
|
|
/* The user just clicked. If he's in the middle of a double-click,
|
|
then we don't want calls to fe_OutlineMakeVisible to cause things
|
|
to scroll before the double-click finishes. So, we set a
|
|
timer to go off; if fe_OutlineMakeVisible gets called before the
|
|
timer expires, we put off the call until the timer goes off. */
|
|
if (info->visibleTimer) {
|
|
FE_ClearTimeout(info->visibleTimer);
|
|
}
|
|
info->visibleTimer =
|
|
FE_SetTimeout(fe_outline_visible_timer, info,
|
|
XtGetMultiClickTime(XtDisplay(info->widget)) + 10);
|
|
}
|
|
if (row != -1)
|
|
fe_OutlineSelect(info->widget, row, True);
|
|
|
|
(*info->clickfunc)(info->widget, info->closure, row, sourcecol - 1,
|
|
info->headers ? info->headers[sourcecol] : NULL,
|
|
event->xbutton.button, numclicks,
|
|
(state & ShiftMask) != 0,
|
|
(state & ControlMask) != 0, 0);
|
|
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
ButtonEvent(Widget widget, XtPointer closure, XEvent* event, Boolean* c)
|
|
{
|
|
fe_OutlineInfo* info = (fe_OutlineInfo*) closure;
|
|
int x = event->xbutton.x;
|
|
int y = event->xbutton.y;
|
|
unsigned char rowtype;
|
|
unsigned char coltype;
|
|
int row;
|
|
int column;
|
|
|
|
info->ignoreevents = False;
|
|
|
|
switch (event->type) {
|
|
case ButtonPress:
|
|
/* Always ignore btn3. Btn3 is for popups. - dp */
|
|
if (event->xbutton.button == 3) break;
|
|
|
|
if (XmLGridXYToRowColumn(info->widget, x, y,
|
|
&rowtype, &row, &coltype, &column) >= 0) {
|
|
if (rowtype == XmHEADING) {
|
|
if (XmLGridPosIsResize(info->widget, x, y)) {
|
|
return;
|
|
}
|
|
}
|
|
info->clickrowtype = rowtype;
|
|
info->dragcolumn = column;
|
|
info->activity |= ButtonPressMask;
|
|
info->ignoreevents = True;
|
|
}
|
|
last_motion_x = x;
|
|
last_motion_y = y;
|
|
info->lastdowntime = event->xbutton.time;
|
|
break;
|
|
|
|
case ButtonRelease:
|
|
|
|
/* fe_SetCursor (info->context, False); */
|
|
|
|
if (info->activity & ButtonPressMask) {
|
|
if (info->activity & PointerMotionMask) {
|
|
/* handle the drop */
|
|
|
|
fe_dnd_DoDrag(&dragsource, event, FE_DND_DROP);
|
|
fe_dnd_DoDrag(&dragsource, event, FE_DND_END);
|
|
|
|
fe_destroy_outline_drag_widget();
|
|
|
|
} else {
|
|
SendClick(info, event, FALSE);
|
|
}
|
|
}
|
|
info->lastuptime = event->xbutton.time;
|
|
info->activity = 0;
|
|
if (info->visibleLine >= 0 && info->visibleTimer == NULL) {
|
|
fe_OutlineMakeVisible(info->widget, info->visibleLine);
|
|
info->visibleLine = -1;
|
|
}
|
|
|
|
break;
|
|
|
|
case MotionNotify:
|
|
if (!info->drag_enabled)
|
|
break;
|
|
|
|
if (!(info->activity & PointerMotionMask) &&
|
|
(abs(x - last_motion_x) < 5 && abs(y - last_motion_y) < 5)) {
|
|
/* We aren't yet dragging, and the mouse hasn't moved enough for
|
|
this to be considered a drag. */
|
|
break;
|
|
}
|
|
|
|
|
|
if (info->activity & ButtonPressMask) {
|
|
/* ok, the pointer moved while a button was held.
|
|
* we're gonna drag some stuff.
|
|
*/
|
|
info->ignoreevents = True;
|
|
if (!(info->activity & PointerMotionMask)) {
|
|
if (info->drag_type == FE_DND_NONE &&
|
|
info->clickrowtype == XmCONTENT) {
|
|
/* We don't do drag'n'drop in this context. Do any any visibility
|
|
scrolling that we may have been putting off til the end of user
|
|
activity. */
|
|
info->activity = 0;
|
|
if (info->visibleLine >= 0 && info->visibleTimer == NULL) {
|
|
fe_OutlineMakeVisible(info->widget, info->visibleLine);
|
|
info->visibleLine = -1;
|
|
}
|
|
break;
|
|
}
|
|
|
|
/* First time. If the item we're pointing at isn't
|
|
selected, deliver a click message for it (which ought to
|
|
select it.) */
|
|
|
|
if (info->clickrowtype == XmCONTENT) {
|
|
/* Hack things so we click where the mouse down was, not where
|
|
the first notify event was. Bleah. */
|
|
event->xbutton.x = last_motion_x;
|
|
event->xbutton.y = last_motion_y;
|
|
SendClick(info, event, TRUE);
|
|
event->xbutton.x = x;
|
|
event->xbutton.y = y;
|
|
}
|
|
|
|
/* Create a drag source. */
|
|
fe_make_outline_drag_widget (info, &info->Data, x, y);
|
|
fe_dnd_DoDrag(&dragsource, event, FE_DND_START);
|
|
info->activity |= PointerMotionMask;
|
|
}
|
|
|
|
fe_dnd_DoDrag(&dragsource, event, FE_DND_DRAG);
|
|
|
|
/* Now, force all the additional mouse motion events that are
|
|
lingering around on the server to come to us, so that Xt can
|
|
compress them away. Yes, XSync really does improve performance
|
|
in this case, not hurt it. */
|
|
XSync(XtDisplay(info->widget), False);
|
|
|
|
}
|
|
last_motion_x = x;
|
|
last_motion_y = y;
|
|
break;
|
|
}
|
|
if (info->ignoreevents) *c = False;
|
|
}
|
|
|
|
static void
|
|
fe_outline_destroy_cb(Widget widget, XtPointer clientData, XtPointer callData)
|
|
{
|
|
fe_OutlineInfo *info = (fe_OutlineInfo*)clientData;
|
|
|
|
/* first we must have something to free... */
|
|
XP_ASSERT(info);
|
|
|
|
/* we must also be sure we're freeing the right stuff. */
|
|
XP_ASSERT(info->widget == widget);
|
|
|
|
FREEIF(info->Data.type);
|
|
FREEIF(info->Data.icons);
|
|
FREEIF(info->Data.str);
|
|
|
|
FREEIF(info->columnIndex);
|
|
FREEIF(info->column_indented);
|
|
FREEIF(info->headerhighlight);
|
|
|
|
XP_FREE(info);
|
|
}
|
|
|
|
static void
|
|
fe_set_default_column_widths(Widget widget) {
|
|
fe_OutlineInfo* info = fe_get_info(widget);
|
|
int i;
|
|
short avgwidth, avgheight;
|
|
XmFontList fontList;
|
|
XtVaGetValues(widget, XmNfontList, &fontList, NULL);
|
|
XmLFontListGetDimensions(fontList, &avgwidth, &avgheight, TRUE);
|
|
|
|
for (i=0 ; i<info->numcolumns ; i++) {
|
|
int width = info->columnwidths[i] * avgwidth;
|
|
if (width < MIN_COLUMN_WIDTH) width = MIN_COLUMN_WIDTH;
|
|
if (width > MAX_COLUMN_WIDTH) width = MAX_COLUMN_WIDTH;
|
|
info->columnIndex[i] = i;
|
|
|
|
if (i == 0) info->iconcolumnwidth = width;
|
|
|
|
XtVaSetValues(widget,
|
|
XmNcolumn, i,
|
|
XmNcolumnSizePolicy, XmCONSTANT,
|
|
XmNcolumnWidth, width,
|
|
NULL);
|
|
}
|
|
}
|
|
|
|
Widget
|
|
fe_GridCreate(MWContext* context, Widget parent, String name,
|
|
ArgList av, Cardinal ac,
|
|
int maxindentdepth,
|
|
int numcolumns, int* columnwidths,
|
|
fe_OutlineGetDataFunc datafunc,
|
|
fe_OutlineClickFunc clickfunc,
|
|
fe_OutlineIconClickFunc iconfunc,
|
|
void* closure,
|
|
char** posinfo, int tag, XP_Bool isOutline)
|
|
{
|
|
Widget result;
|
|
fe_OutlineInfo* info;
|
|
|
|
XP_ASSERT(numcolumns >= 0);
|
|
|
|
info = XP_NEW_ZAP(fe_OutlineInfo);
|
|
if (info == NULL) return NULL; /* ### */
|
|
|
|
XtSetArg(av[ac], XmNuserData, info); ac++;
|
|
|
|
info->numcolumns = numcolumns;
|
|
|
|
info->datafunc = datafunc;
|
|
info->clickfunc = clickfunc;
|
|
info->iconfunc = iconfunc;
|
|
info->closure = closure;
|
|
info->dragrow = -1;
|
|
info->posinfo = posinfo;
|
|
info->visibleLine = -1;
|
|
info->DataRow = -1;
|
|
|
|
info->Data.type = XP_CALLOC(numcolumns, sizeof(fe_OutlineType));
|
|
info->Data.icons = XP_CALLOC(numcolumns, sizeof(fe_icon*));
|
|
info->Data.str = XP_CALLOC(numcolumns, sizeof(char*));
|
|
|
|
info->lastx = -999;
|
|
info->columnwidths = columnwidths;
|
|
|
|
XtSetArg(av[ac], XmNselectionPolicy, XmSELECT_MULTIPLE_ROW); ac++;
|
|
|
|
if (isOutline) {
|
|
columnwidths[numcolumns - 1] = 9999; /* Make the last column always really
|
|
wide, so we always are ready to
|
|
show something no matter how wide
|
|
the window gets. */
|
|
|
|
if ((context->type == MWContextMail
|
|
&& fe_globalPrefs.mail_pane_style == FE_PANES_HORIZONTAL)
|
|
|| ((context->type == MWContextNews
|
|
&& fe_globalPrefs.news_pane_style == FE_PANES_HORIZONTAL))) {
|
|
XtSetArg(av[ac], XmNhorizontalSizePolicy, XmCONSTANT); ac++;
|
|
}
|
|
else {
|
|
XtSetArg(av[ac], XmNhorizontalSizePolicy, XmVARIABLE); ac++;
|
|
}
|
|
} else {
|
|
XtSetArg(av[ac], XmNhorizontalSizePolicy, XmCONSTANT); ac++;
|
|
}
|
|
|
|
result = XmLCreateGrid(parent, name, av, ac);
|
|
info->widget = result;
|
|
|
|
XtAddCallback(result, XmNdestroyCallback, fe_outline_destroy_cb, info);
|
|
|
|
XtVaSetValues(result,
|
|
XmNcellDefaults, True,
|
|
XmNcellLeftBorderType, XmBORDER_NONE,
|
|
XmNcellRightBorderType, XmBORDER_NONE,
|
|
XmNcellTopBorderType, XmBORDER_NONE,
|
|
XmNcellBottomBorderType, XmBORDER_NONE,
|
|
XmNcellAlignment, XmALIGNMENT_LEFT,
|
|
0);
|
|
|
|
XmLGridAddColumns(result, XmCONTENT, -1, info->numcolumns);
|
|
|
|
info->allowiconresize = (maxindentdepth > 0);
|
|
|
|
info->columnIndex = XP_CALLOC(numcolumns, sizeof(int));
|
|
|
|
fe_set_default_column_widths(result);
|
|
|
|
XtInsertEventHandler(result, ButtonPressMask | ButtonReleaseMask
|
|
| PointerMotionMask, False,
|
|
ButtonEvent, info, XtListHead);
|
|
|
|
XtAddCallback(result, XmNcellDrawCallback, fe_outline_celldraw, info);
|
|
/* XtAddCallback(result, XmNselectCallback, fe_outline_click, info);
|
|
XtAddCallback(result, XmNactivateCallback, fe_outline_click, info);*/
|
|
|
|
/* initialize the column indentation stuff. */
|
|
info->column_indented = XP_CALLOC(numcolumns, sizeof(int *));
|
|
info->indent_amount = 10;
|
|
|
|
/* initialize the drag and drop stuff. */
|
|
fe_OutlineDisableDrag(result);
|
|
info->dragColumnPixmap = 0;
|
|
|
|
if (!pixmapFlippyFolded.pixmap) {
|
|
Pixel pixel;
|
|
XtVaGetValues(result, XmNbackground, &pixel, NULL);
|
|
|
|
fe_MakeIcon(context, pixel, &pixmapFlippyFolded, NULL,
|
|
HFolder.width, HFolder.height,
|
|
HFolder.mono_bits, HFolder.color_bits, HFolder.mask_bits,
|
|
FALSE);
|
|
fe_MakeIcon(context, pixel, &pixmapFlippyExpanded, NULL,
|
|
HFolderO.width, HFolderO.height,
|
|
HFolderO.mono_bits, HFolderO.color_bits, HFolderO.mask_bits,
|
|
FALSE);
|
|
#if 0
|
|
/* no marks. */
|
|
fe_MakeIcon(context, pixel, &pixmapMarked, NULL,
|
|
HMarked.width, HMarked.height,
|
|
HMarked.mono_bits, HMarked.color_bits, HMarked.mask_bits,
|
|
FALSE);
|
|
fe_MakeIcon(context, pixel, &pixmapUnmarked, NULL,
|
|
HUMarked.width, HUMarked.height,
|
|
HUMarked.mono_bits, HUMarked.color_bits, HUMarked.mask_bits,
|
|
FALSE);
|
|
#endif
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
fe_outline_remember_columns(Widget widget)
|
|
{
|
|
fe_OutlineInfo* info = fe_get_info(widget);
|
|
XmLCGridColumn* col;
|
|
int i;
|
|
Dimension width;
|
|
char* ptr;
|
|
int length = 0;
|
|
FREEIF(*(info->posinfo));
|
|
for (i=0 ; i < info->numcolumns ; i++) {
|
|
length += strlen(info->headers[i]) + 14;
|
|
}
|
|
*(info->posinfo) = XP_ALLOC(length);
|
|
ptr = *(info->posinfo);
|
|
for (i=0 ; i<info->numcolumns ; i++) {
|
|
col = XmLGridGetColumn(widget, XmCONTENT, i);
|
|
XtVaGetValues(widget, XmNcolumnPtr, col, XmNcolumnWidth, &width, 0);
|
|
PR_snprintf(ptr, *(info->posinfo) + length - ptr,
|
|
"%s:%d;", info->headers[info->columnIndex[i]],
|
|
(int) width);
|
|
ptr += strlen(ptr);
|
|
}
|
|
if (ptr > *(info->posinfo)) ptr[-1] = '\0';
|
|
}
|
|
|
|
|
|
static void
|
|
fe_outline_resize(Widget widget, XtPointer clientData, XtPointer callData)
|
|
{
|
|
/* The user has resized a column. Unfortunately, if they resize it
|
|
to width 0, they will never be able to grab it to resize it
|
|
bigger. So, we will implement a minimum width per column. */
|
|
|
|
fe_OutlineInfo* info = (fe_OutlineInfo*) clientData;
|
|
XmLGridCallbackStruct* call = (XmLGridCallbackStruct*) callData;
|
|
XmLCGridColumn* col;
|
|
Dimension width;
|
|
if (call->reason != XmCR_RESIZE_COLUMN) return;
|
|
if (!info->allowiconresize && call->column == 0) {
|
|
XtVaSetValues(widget, XmNcolumn, call->column, XmNcolumnWidth,
|
|
info->iconcolumnwidth, 0);
|
|
} else {
|
|
col = XmLGridGetColumn(widget, call->columnType, call->column);
|
|
XtVaGetValues(widget, XmNcolumnPtr, col, XmNcolumnWidth, &width, 0);
|
|
if (width < MIN_COLUMN_WIDTH) {
|
|
XtVaSetValues(widget, XmNcolumn, call->column,
|
|
XmNcolumnWidth, MIN_COLUMN_WIDTH, 0);
|
|
}
|
|
}
|
|
fe_outline_remember_columns(widget);
|
|
}
|
|
|
|
void
|
|
fe_OutlineSetMaxDepth(Widget outline, int maxdepth)
|
|
{
|
|
fe_OutlineInfo* info = fe_get_info(outline);
|
|
int value = (maxdepth - 1) * PIXMAP_INDENT + PIXMAP_WIDTH + FLIPPY_WIDTH;
|
|
XP_ASSERT(!info->allowiconresize);
|
|
XP_ASSERT(maxdepth > 0);
|
|
if (value != info->iconcolumnwidth) {
|
|
info->iconcolumnwidth = value;
|
|
XtVaSetValues(outline, XmNcolumn, 0, XmNcolumnWidth, value, 0);
|
|
}
|
|
}
|
|
|
|
|
|
static XmString
|
|
fe_outline_get_header(char *widget, char *header, XP_Bool highlight)
|
|
{
|
|
char clas[512];
|
|
XrmDatabase db;
|
|
char name[512];
|
|
char *type;
|
|
XrmValue value;
|
|
XmString xms;
|
|
|
|
db = XtDatabase(fe_display);
|
|
(void) PR_snprintf(clas, sizeof(clas), "%s.MailNewsColumns.Pane.Column",
|
|
fe_progclass);
|
|
(void) PR_snprintf(name, sizeof(name), "%s.mailNewsColumns.%s.%s",
|
|
fe_progclass, widget, header);
|
|
if (XrmGetResource(db, name, clas, &type, &value))
|
|
{
|
|
xms = XmStringCreate((char *) value.addr,
|
|
highlight ? BOLD_STYLE : DEFAULT_STYLE);
|
|
}
|
|
else
|
|
{
|
|
xms = XmStringCreate(header,
|
|
highlight ? BOLD_STYLE : DEFAULT_STYLE);
|
|
}
|
|
|
|
return xms;
|
|
}
|
|
|
|
|
|
void
|
|
fe_OutlineSetHeaders(Widget widget, fe_OutlineHeaderDesc *desc)
|
|
{ /* Fix i18n in here ### */
|
|
fe_OutlineInfo* info = fe_get_info(widget);
|
|
int i;
|
|
XmString str;
|
|
char* ptr;
|
|
char* ptr2;
|
|
char* ptr3;
|
|
int value;
|
|
int width;
|
|
|
|
ptr = *info->posinfo;
|
|
for (i=0 ; i<info->numcolumns ; i++) {
|
|
if (ptr == NULL) {
|
|
FREEIF(*info->posinfo);
|
|
break;
|
|
}
|
|
ptr2 = XP_STRCHR(ptr, ';');
|
|
if (ptr2) *ptr2 = '\0';
|
|
ptr3 = XP_STRCHR(ptr, ':');
|
|
if (!ptr3) {
|
|
FREEIF(*info->posinfo);
|
|
break;
|
|
}
|
|
*ptr3 = '\0';
|
|
for (value = 0 ; value < info->numcolumns ; value++) {
|
|
if (strcmp(desc->header_strings[value], ptr) == 0) break;
|
|
}
|
|
if (value > info->numcolumns) {
|
|
FREEIF(*info->posinfo);
|
|
break;
|
|
}
|
|
info->columnIndex[i] = value;
|
|
width = atoi(ptr3 + 1);
|
|
*ptr3 = ':';
|
|
if (i == info->numcolumns) width = MAX_COLUMN_WIDTH; /* Last column is
|
|
always huge.*/
|
|
if (width < MIN_COLUMN_WIDTH) width = MIN_COLUMN_WIDTH;
|
|
if (width > MAX_COLUMN_WIDTH) width = MAX_COLUMN_WIDTH;
|
|
XtVaSetValues(widget,
|
|
XmNcolumn, i,
|
|
XmNcolumnSizePolicy, XmCONSTANT,
|
|
XmNcolumnWidth, width,
|
|
0);
|
|
if (ptr2) *ptr2++ = ';';
|
|
ptr = ptr2;
|
|
}
|
|
|
|
if (*info->posinfo) {
|
|
/* Check that every column was mentioned, and no duplicates occurred. */
|
|
int* check = XP_CALLOC(info->numcolumns, sizeof(int));
|
|
for (i=0 ; i<info->numcolumns ; i++) check[i] = 0;
|
|
for (i=0 ; i<info->numcolumns ; i++) {
|
|
int w = info->columnIndex[i];
|
|
if (w < 0 || w > info->numcolumns || check[w]) {
|
|
FREEIF(*info->posinfo);
|
|
break;
|
|
}
|
|
check[w] = 1;
|
|
}
|
|
XP_FREE(check);
|
|
}
|
|
|
|
if (!*info->posinfo) fe_set_default_column_widths(widget);
|
|
|
|
info->headers = desc->header_strings;
|
|
info->headerhighlight = (XP_Bool*)
|
|
XP_ALLOC((info->numcolumns) * sizeof(XP_Bool));
|
|
XP_MEMSET(info->headerhighlight, 0,
|
|
(info->numcolumns) * sizeof(XP_Bool));
|
|
XmLGridAddRows(widget, XmHEADING, 0, 1);
|
|
for (i=0 ; i<info->numcolumns ; i++) {
|
|
char* name = (char *) desc->header_strings[info->columnIndex[i]];
|
|
|
|
if (desc->type[info->columnIndex[i]] & FE_OUTLINE_String)
|
|
{
|
|
str = fe_outline_get_header(XtName(widget), name, 0);
|
|
XtVaSetValues(widget, XmNcolumn, i, XmNrowType, XmHEADING, XmNrow, 0,
|
|
XmNcellLeftBorderType, XmBORDER_LINE,
|
|
XmNcellRightBorderType, XmBORDER_LINE,
|
|
XmNcellTopBorderType, XmBORDER_LINE,
|
|
XmNcellBottomBorderType, XmBORDER_LINE,
|
|
XmNcellString, str, 0);
|
|
XmStringFree(str);
|
|
}
|
|
else if (desc->type[info->columnIndex[i]] & FE_OUTLINE_Icon)
|
|
{
|
|
XtVaSetValues(widget, XmNcolumn, i, XmNrowType, XmHEADING, XmNrow, 0,
|
|
XmNcellLeftBorderType, XmBORDER_LINE,
|
|
XmNcellRightBorderType, XmBORDER_LINE,
|
|
XmNcellTopBorderType, XmBORDER_LINE,
|
|
XmNcellBottomBorderType, XmBORDER_LINE,
|
|
XmNcellType, XmPIXMAP_CELL, XmNcellPixmap, desc->icons[info->columnIndex[i]]->pixmap,
|
|
0);
|
|
}
|
|
|
|
/* keep track of the column information -- whether or not to indent, draw
|
|
lines, etc. */
|
|
info->column_indented[i] = desc->type[info->columnIndex[i]];
|
|
}
|
|
XtVaSetValues(widget, XmNallowColumnResize, True, 0);
|
|
XtAddCallback(widget, XmNresizeCallback, fe_outline_resize, info);
|
|
}
|
|
|
|
|
|
void
|
|
fe_OutlineChangeHeaderLabel(Widget widget, const char* headername,
|
|
const char* label)
|
|
{
|
|
fe_OutlineInfo* info = fe_get_info(widget);
|
|
int i;
|
|
int j;
|
|
XmString str;
|
|
if (label == NULL) label = headername;
|
|
for (i=0 ; i<info->numcolumns ; i++) {
|
|
unsigned char celltype;
|
|
XtVaGetValues(widget, XmNcellType, &celltype, 0);
|
|
|
|
if (XP_STRCMP(info->headers[i], headername) == 0) {
|
|
for (j=0 ; j<info->numcolumns ; j++) {
|
|
if (info->columnIndex[j] != i) continue;
|
|
str = XmStringCreate((char*/*NOOP*/)label,
|
|
info->headerhighlight[i] ? BOLD_STYLE : DEFAULT_STYLE);
|
|
XtVaSetValues(widget, XmNcolumn, j, XmNrowType, XmHEADING, XmNrow, 0,
|
|
XmNcellString, str,
|
|
XmNcellType, XmSTRING_CELL,
|
|
XmNcellEditable, False, /* hmm.. */
|
|
0);
|
|
XmStringFree(str);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
XP_ASSERT(0);
|
|
}
|
|
|
|
|
|
void
|
|
fe_OutlineSetHeaderHighlight(Widget widget, const char* header, XP_Bool value)
|
|
{
|
|
fe_OutlineInfo* info = fe_get_info(widget);
|
|
int i, j;
|
|
XmString str;
|
|
for (i=0 ; i<info->numcolumns ; i++) {
|
|
if (XP_STRCMP(info->headers[i], header) == 0) {
|
|
if (info->headerhighlight[i] == value) return;
|
|
info->headerhighlight[i] = value;
|
|
for (j=0 ; j<info->numcolumns ; j++) {
|
|
if (info->columnIndex[j] == i) {
|
|
str = fe_outline_get_header(XtName(widget), (char *)info->headers[i], value);
|
|
XtVaSetValues(widget, XmNcolumn, j, XmNrowType, XmHEADING,
|
|
XmNrow, 0, XmNcellString, str, 0);
|
|
XmStringFree(str);
|
|
return;
|
|
}
|
|
}
|
|
XP_ASSERT(0);
|
|
}
|
|
}
|
|
/* should we really do this?
|
|
- toshok
|
|
XP_ASSERT(0);
|
|
*/
|
|
}
|
|
|
|
|
|
|
|
void
|
|
fe_OutlineChange(Widget widget, int first, int length, int newnumrows)
|
|
{
|
|
fe_OutlineInfo* info = fe_get_info(widget);
|
|
int i;
|
|
info->DataRow = -1;
|
|
if (newnumrows != info->numrows) {
|
|
if (newnumrows > info->numrows) {
|
|
XmLGridAddRows(widget, XmCONTENT, -1, newnumrows - info->numrows);
|
|
} else {
|
|
XmLGridDeleteRows(widget, XmCONTENT, newnumrows,
|
|
info->numrows - newnumrows);
|
|
}
|
|
info->numrows = newnumrows;
|
|
length = newnumrows - first;
|
|
}
|
|
if (first == 0 && length == newnumrows) {
|
|
XmLGridRedrawAll(widget);
|
|
} else {
|
|
for (i=first ; i<first + length ; i++) {
|
|
XmLGridRedrawRow(widget, XmCONTENT, i);
|
|
}
|
|
}
|
|
XFlush(XtDisplay(widget));
|
|
}
|
|
|
|
|
|
void
|
|
fe_OutlineSelect(Widget widget, int row, Boolean exclusive)
|
|
{
|
|
if (exclusive) XmLGridDeselectAllRows(widget, False);
|
|
XmLGridSelectRow(widget, row, False);
|
|
}
|
|
|
|
|
|
void
|
|
fe_OutlineUnselect(Widget widget, int row)
|
|
{
|
|
XmLGridDeselectRow(widget, row, False);
|
|
}
|
|
|
|
void
|
|
fe_OutlineUnselectAll(Widget widget)
|
|
{
|
|
XmLGridDeselectAllRows(widget, False);
|
|
}
|
|
|
|
|
|
void
|
|
fe_OutlineMakeVisible(Widget widget, int visible)
|
|
{
|
|
fe_OutlineInfo* info = fe_get_info(widget);
|
|
int firstrow, lastrow;
|
|
XRectangle rect;
|
|
Dimension height, shadowthickness;
|
|
if (visible < 0) return;
|
|
if (info->visibleTimer) {
|
|
info->visibleLine = visible;
|
|
return;
|
|
}
|
|
XtVaGetValues(widget, XmNscrollRow, &firstrow, XmNheight, &height,
|
|
XmNshadowThickness, &shadowthickness, NULL);
|
|
height -= shadowthickness;
|
|
for (lastrow = firstrow + 1 ; ; lastrow++) {
|
|
if (XmLGridRowColumnToXY(widget, XmCONTENT, lastrow, XmCONTENT, 0,
|
|
True, &rect) < 0)
|
|
{
|
|
break;
|
|
}
|
|
if (rect.y + rect.height >= (int)height) break;
|
|
}
|
|
if (visible >= firstrow && visible < lastrow) return;
|
|
firstrow = visible - ((lastrow - firstrow) / 2);
|
|
if (firstrow < 0) firstrow = 0;
|
|
XtVaSetValues(widget, XmNscrollRow, firstrow, 0);
|
|
}
|
|
|
|
|
|
int
|
|
fe_OutlineGetSelection(Widget outline, MSG_ViewIndex* sellist, int sizesellist)
|
|
{
|
|
int count = XmLGridGetSelectedRowCount(outline);
|
|
if (sellist && count > 0) {
|
|
int status;
|
|
XP_ASSERT(count <= sizesellist);
|
|
if (sizeof(MSG_ViewIndex) == sizeof(int)) {
|
|
status = XmLGridGetSelectedRows(outline, (int*) sellist, count);
|
|
} else {
|
|
int* positions = (int*) XP_ALLOC(count * sizeof(int));
|
|
int i;
|
|
status = XmLGridGetSelectedRows(outline, positions, count);
|
|
if (status == 0) {
|
|
for (i=0 ; i<count ; i++) {
|
|
sellist[i] = positions[i];
|
|
}
|
|
}
|
|
XP_FREE(positions);
|
|
}
|
|
if (status < 0) return status;
|
|
}
|
|
return count;
|
|
}
|
|
|
|
|
|
int
|
|
fe_OutlineRootCoordsToRow(Widget outline, int x, int y, XP_Bool* nearbottom)
|
|
{
|
|
Position rootx;
|
|
Position rooty;
|
|
int row;
|
|
int column;
|
|
unsigned char rowtype;
|
|
unsigned char coltype;
|
|
XtTranslateCoords(outline, 0, 0, &rootx, &rooty);
|
|
x -= rootx;
|
|
y -= rooty;
|
|
if (x < 0 || y < 0 ||
|
|
x >= XfeWidth(outline) || y >= XfeHeight(outline)) {
|
|
return -1;
|
|
}
|
|
if (XmLGridXYToRowColumn(outline, x, y, &rowtype, &row,
|
|
&coltype, &column) < 0) {
|
|
return -1;
|
|
}
|
|
if (rowtype != XmCONTENT || coltype != XmCONTENT) return -1;
|
|
if (nearbottom) {
|
|
int row2;
|
|
*nearbottom = (XmLGridXYToRowColumn(outline, x, y + 5, &rowtype, &row2,
|
|
&coltype, &column) >= 0 &&
|
|
row2 > row);
|
|
}
|
|
return row;
|
|
}
|
|
|
|
|
|
#define SCROLLMARGIN 50
|
|
#define INITIALWAIT 500
|
|
#define REPEATINTERVAL 100
|
|
|
|
static void
|
|
fe_outline_drag_scroll(void* closure)
|
|
{
|
|
fe_OutlineInfo* info = (fe_OutlineInfo*) closure;
|
|
int row;
|
|
info->dragscrolltimer = FE_SetTimeout(fe_outline_drag_scroll, info,
|
|
REPEATINTERVAL);
|
|
XtVaGetValues(info->widget, XmNscrollRow, &row, 0);
|
|
row += info->dragscrolldir;
|
|
XtVaSetValues(info->widget, XmNscrollRow, row, 0);
|
|
}
|
|
|
|
void
|
|
fe_OutlineHandleDragEvent(Widget outline, XEvent* event, fe_dnd_Event type,
|
|
fe_dnd_Source* source)
|
|
{
|
|
fe_OutlineInfo* info = fe_get_info(outline);
|
|
XP_Bool doscroll = FALSE;
|
|
Position rootx;
|
|
Position rooty;
|
|
int x, y;
|
|
unsigned char rowtype;
|
|
unsigned char coltype;
|
|
int row;
|
|
int column;
|
|
XmLCGridColumn* col;
|
|
Dimension width;
|
|
int i;
|
|
int delta;
|
|
int tmp;
|
|
int total;
|
|
|
|
if (source->type == FE_DND_COLUMN) {
|
|
if (type == FE_DND_DROP && source->closure == outline)
|
|
{
|
|
/* first we make sure that the drop happens within a valid row/column
|
|
pair. */
|
|
|
|
XtTranslateCoords(outline, 0, 0, &rootx, &rooty);
|
|
|
|
x = event->xbutton.x_root - rootx;
|
|
y = event->xbutton.y_root - rooty;
|
|
|
|
if (XmLGridXYToRowColumn(info->widget, x, y,
|
|
&rowtype, &row, &coltype, &column) < 0)
|
|
return;
|
|
|
|
if (column != info->dragcolumn)
|
|
{
|
|
/* Get rid of the hack that makes the last column really wide. Make
|
|
it be exactly as wide as it appears, so that if it no longer
|
|
ends up as the last column, it has a reasonable width. */
|
|
total = XfeWidth(outline);
|
|
for (i=0 ; i<info->numcolumns ; i++)
|
|
{
|
|
col = XmLGridGetColumn(outline, XmCONTENT, i);
|
|
XtVaGetValues(outline,
|
|
XmNcolumnPtr, col,
|
|
XmNcolumnWidth, &width, 0
|
|
);
|
|
total -= ((int) width); /* Beware any unsigned lossage... */
|
|
}
|
|
if (total < MIN_COLUMN_WIDTH) total = MIN_COLUMN_WIDTH;
|
|
width = total;
|
|
if (width > MAX_COLUMN_WIDTH) width = MAX_COLUMN_WIDTH;
|
|
XtVaSetValues(outline, XmNcolumn, info->numcolumns - 1,
|
|
XmNcolumnWidth, width, 0);
|
|
|
|
if (info->dragcolumn < column)
|
|
{
|
|
delta = 1;
|
|
}
|
|
else
|
|
{
|
|
delta = -1;
|
|
}
|
|
|
|
/* save off the column number we dragged */
|
|
tmp = info->columnIndex[info->dragcolumn];
|
|
|
|
/* move all the other columns out of the way. */
|
|
for (i=info->dragcolumn ; i != column ; i += delta)
|
|
{
|
|
info->columnIndex[i] = info->columnIndex[i + delta];
|
|
}
|
|
|
|
/* replace the column we dragged in the place we dropped
|
|
it. */
|
|
info->columnIndex[column] = tmp;
|
|
|
|
XmLGridMoveColumns(outline, column, info->dragcolumn, 1);
|
|
|
|
/* Now restore the hack of having the last column be wide. */
|
|
XtVaSetValues(outline, XmNcolumn, info->numcolumns - 1,
|
|
XmNcolumnWidth, MAX_COLUMN_WIDTH, 0);
|
|
|
|
fe_outline_remember_columns(outline);
|
|
}
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
if (type != FE_DND_DRAG && type != FE_DND_END) return;
|
|
|
|
if (type == FE_DND_DRAG) {
|
|
XtTranslateCoords(outline, 0, 0, &rootx, &rooty);
|
|
x = event->xbutton.x_root - rootx;
|
|
y = event->xbutton.y_root - rooty;
|
|
doscroll = (x >= 0 && x < XfeWidth(outline) &&
|
|
((y < 0 && y >= -SCROLLMARGIN) ||
|
|
(y >= XfeHeight(outline) &&
|
|
y < XfeHeight(outline) + SCROLLMARGIN)));
|
|
info->dragscrolldir = y > XfeHeight(outline) / 2 ? 1 : -1;
|
|
}
|
|
if (!doscroll) {
|
|
if (info->dragscrolltimer) {
|
|
FE_ClearTimeout(info->dragscrolltimer);
|
|
info->dragscrolltimer = NULL;
|
|
}
|
|
} else {
|
|
if (!info->dragscrolltimer) {
|
|
info->dragscrolltimer = FE_SetTimeout(fe_outline_drag_scroll, info,
|
|
INITIALWAIT);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void
|
|
fe_OutlineSetDragFeedback(Widget outline, int row, XP_Bool usebox)
|
|
{
|
|
fe_OutlineInfo* info = fe_get_info(outline);
|
|
int old = info->dragrow;
|
|
if (old == row && info->dragrowbox == usebox) return;
|
|
info->dragrowbox = usebox;
|
|
info->dragrow = row;
|
|
if (old >= 0) XmLGridRedrawRow(outline, XmCONTENT, old);
|
|
if (row >= 0 && row != old) XmLGridRedrawRow(outline, XmCONTENT, row);
|
|
}
|
|
|
|
|
|
Widget
|
|
fe_OutlineCreate(MWContext* context, Widget parent, String name,
|
|
ArgList av, Cardinal ac,
|
|
int maxindentdepth,
|
|
int numcolumns, int* columnwidths,
|
|
fe_OutlineGetDataFunc datafunc,
|
|
fe_OutlineClickFunc clickfunc,
|
|
fe_OutlineIconClickFunc iconfunc,
|
|
void* closure,
|
|
char** posinfo, int tag)
|
|
{
|
|
/*int i;*/
|
|
|
|
return fe_GridCreate(context, parent, name, av, ac, maxindentdepth,
|
|
numcolumns, columnwidths, datafunc, clickfunc,
|
|
iconfunc, closure, posinfo, tag, True);
|
|
/* last var = isOutline: attach TRUE for Outline, attach FALSE for Grid */
|
|
}
|
|
|
|
void fe_OutlineEnableDrag(Widget outline, fe_icon *dragicon, fe_dnd_Type dragtype)
|
|
{
|
|
fe_OutlineInfo* info = fe_get_info(outline);
|
|
|
|
info->drag_enabled = True;
|
|
info->drag_icon = dragicon;
|
|
info->drag_type = dragtype;
|
|
}
|
|
|
|
void fe_OutlineDisableDrag(Widget outline)
|
|
{
|
|
fe_OutlineInfo* info = fe_get_info(outline);
|
|
|
|
info->drag_enabled = False;
|
|
info->drag_icon = NULL;
|
|
info->drag_type = FE_DND_NONE;
|
|
}
|