package X11::Motif; # Copyright 1997, 1998 by Ken Fox use DynaLoader; use strict; use vars qw($VERSION @ISA); BEGIN { $VERSION = 1.1; @ISA = qw(DynaLoader); # A widget set is responsible for loading itself and the # X Toolkit. This is due to problems with the Xt library # when linked independently from a widget set -- the # Toolkit's definition of symbols such as vendorShell seem # to be corrupt or incompatible with a widget set's. bootstrap X11::Motif; bootstrap X11::Toolkit; use X11::Toolkit qw(:private); use X11::MotifCons; # Define the standard Toolkit aliases -- this has to be # done here to ensure that all the Toolkit symbols have # been constructed. X11::Toolkit::use_standard_aliases(); } sub beta_version { 2 }; sub import { my $module = shift; my %done; foreach my $sym (@_) { next if ($done{$sym}); if ($sym eq ':X') { export_pattern(\%X::, '^X'); } elsif ($sym eq ':Xt') { export_pattern(\%X::Toolkit::, '^Xt'); export_pattern(\%X::Toolkit::Context::, '^Xt'); export_pattern(\%X::Toolkit::Widget::, '^Xt'); } elsif ($sym eq ':Xm') { if (!$done{':widgets'}) { $done{':widgets'} = 1; export_pattern(\%X::Motif::, '^xm'); } export_pattern(\%X::Motif::, '^Xm'); } elsif ($sym eq ':widgets') { export_pattern(\%X::Motif::, '^xm'); } elsif ($sym eq ':private') { export_symbol(\%X11::Lib::, 'export_pattern'); export_symbol(\%X11::Lib::, 'export_symbol'); export_symbol(\%X11::Lib::, 'alias_trimmed_pattern'); } else { export_symbol(\%X::Motif::, $sym); } $done{$sym} = 1; } } my $finished_standard_aliases = 0; sub use_standard_aliases { if (!$finished_standard_aliases) { $finished_standard_aliases = 1; # this next line might not be something we want to do... there # are an awful lot of XmN resources and they might look better # if they weren't aliased. #alias_trimmed_pattern("X::Motif", \%X::Motif::, '^Xm'); } } package X::Motif; use Carp; # ================================================================================ # Motif Widgets # # Register the Motif widgets under their full names, e.g. XmLabel, # XmPushButton, XmForm. The arguments to register() form the aliases (i.e. # short intuitive resource names) understood by the widget. Any aliases # defined on the widget's superclass will be inherited by the widget. However, # Motif has many widgets which use identical resources that aren't inherited. # For these widgets, we just define a small list and re-use the list in the # call to register(). $X::Toolkit::Widget::resource_hints{'BooleanDimension'} = 'u'; $X::Toolkit::Widget::resource_hints{'HorizontalDimension'} = 'u'; $X::Toolkit::Widget::resource_hints{'VerticalDimension'} = 'u'; { my @activate = ('command' => 'activateCallback'); # ------------------------------------------------------------ # Primitives xmArrowButtonWidgetClass()->register(@activate); xmLabelWidgetClass()->register('text' => ['labelString', 'labelType' => 'string'], 'icon' => ['labelPixmap', 'labelType' => 'pixmap'], 'font' => 'fontList'); xmCascadeButtonWidgetClass()->register(@activate); xmDrawnButtonWidgetClass()->register(@activate); xmPushButtonWidgetClass()->register(@activate); xmToggleButtonWidgetClass()->register('command' => 'valueChangedCallback'); xmListWidgetClass()->register(); xmScrollBarWidgetClass()->register(); xmSeparatorWidgetClass()->register(); xmTextWidgetClass()->register('text' => 'value'); xmTextFieldWidgetClass()->register('text' => 'value', @activate); # ------------------------------------------------------------ # Managers xmBulletinBoardWidgetClass()->register(); xmFormWidgetClass()->register(); xmSelectionBoxWidgetClass()->register(); xmCommandWidgetClass()->register(); xmFileSelectionBoxWidgetClass()->register(); xmMessageBoxWidgetClass()->register('message' => 'messageString', 'alignment' => 'messageAlignment'); xmDrawingAreaWidgetClass()->register(); xmFrameWidgetClass()->register(); xmPanedWindowWidgetClass()->register(); xmRowColumnWidgetClass()->register('label' => 'labelString'); xmScaleWidgetClass()->register(); xmScrolledWindowWidgetClass()->register(); xmMainWindowWidgetClass()->register(); # ------------------------------------------------------------ # Shells overrideShellWidgetClass()->register(); wmShellWidgetClass()->register('resizable' => 'allowShellResize'); vendorShellWidgetClass()->register(); transientShellWidgetClass()->register(); xmMenuShellWidgetClass()->register(); xmDialogShellWidgetClass()->register(); topLevelShellWidgetClass()->register(); applicationShellWidgetClass()->register(); # ------------------------------------------------------------ # Custom Motif Extensions xpFolderWidgetClass()->register(); xpStackWidgetClass()->register(); xpLinedAreaWidgetClass()->register(); } sub create_menu { my $parent = shift; my $type = shift; my $shell = X::Toolkit::CreatePopupShell("a_menu_shell", xmMenuShellWidgetClass, $parent, XmNwidth, 1, XmNheight, 1); my $rc = X::Toolkit::CreateWidget("a_menu", xmRowColumnWidgetClass, $shell, XmNrowColumnType, XmMENU_PULLDOWN); my $button = give $parent xmCascadeButtonWidgetClass, XmNsubMenuId, $rc, @_; if ($parent->IsSubclass(xmRowColumnWidgetClass) && (query $parent XmNrowColumnType) == XmMENU_BAR) { my $label = query $button -text; if (plain $label =~ /\bHELP\b/i) { change $parent -menuHelpWidget => $button; } } $rc; } sub create_option_menu { my $parent = shift; my $type = shift; my $shell = X::Toolkit::CreatePopupShell("a_menu_shell", xmMenuShellWidgetClass, $parent, XmNwidth, 1, XmNheight, 1); my $rc = X::Toolkit::CreateWidget("a_menu", xmRowColumnWidgetClass, $shell, XmNrowColumnType, XmMENU_PULLDOWN); my $opt = give $parent xmRowColumnWidgetClass, XmNrowColumnType, XmMENU_OPTION, XmNsubMenuId, $rc, @_; return ($opt, $rc); } sub create_popup_menu { my $parent = shift; my $type = shift; my $shell = X::Toolkit::CreatePopupShell("a_menu_shell", xmMenuShellWidgetClass, $parent, XmNwidth, 1, XmNheight, 1); my $rc = give $shell xmRowColumnWidgetClass, -rowColumnType => XmMENU_POPUP, -managed => X::False, @_; return $rc; } sub XmDIALOG_CHOICE () { 10 } my %dialog_style_names = ( 'error' => X::Motif::XmDIALOG_ERROR, 'info' => X::Motif::XmDIALOG_INFORMATION, 'information' => X::Motif::XmDIALOG_INFORMATION, 'message' => X::Motif::XmDIALOG_MESSAGE, 'question' => X::Motif::XmDIALOG_QUESTION, 'warning' => X::Motif::XmDIALOG_WARNING, 'working' => X::Motif::XmDIALOG_WORKING, 'busy' => X::Motif::XmDIALOG_WORKING, 'choice' => X::Motif::XmDIALOG_CHOICE(), 'option' => X::Motif::XmDIALOG_CHOICE() ); my @dialog_style_titles; $dialog_style_titles[X::Motif::XmDIALOG_ERROR] = 'Error!'; $dialog_style_titles[X::Motif::XmDIALOG_INFORMATION] = 'Information'; $dialog_style_titles[X::Motif::XmDIALOG_MESSAGE] = 'Message'; $dialog_style_titles[X::Motif::XmDIALOG_QUESTION] = 'Confirm'; $dialog_style_titles[X::Motif::XmDIALOG_WARNING] = 'Warning!'; $dialog_style_titles[X::Motif::XmDIALOG_WORKING] = 'Working ...'; $dialog_style_titles[X::Motif::XmDIALOG_CHOICE()] = 'Choose'; sub create_dialog { my $parent = shift; my $type = shift; my @options = (); my $style = X::Motif::XmDIALOG_MESSAGE; my %show; my $choices; my $title; my($res_name, $value); my $num = scalar @_; my $i = 0; while ($i < $num) { $res_name = $_[$i++]; $res_name =~ s|^-||; $value = $_[$i++]; if ($res_name eq 'style' || $res_name eq 'type') { $style = $value; } elsif ($res_name eq 'choices') { $choices = $value; } elsif ($res_name eq 'title') { $title = $value; } elsif ($res_name eq 'ok' || $res_name eq 'cancel' || $res_name eq 'help') { if (ref $value eq 'ARRAY') { push @options, $res_name.'LabelString' => $value->[0], $res_name.'Callback' => $value->[1]; } else { push @options, $res_name.'Callback' => $value; } $show{$res_name} = 1; } else { push @options, $res_name => $value; } } if (X::is_string($style)) { $style =~ s|^-||; if (defined $dialog_style_names{$style}) { $style = $dialog_style_names{$style}; } } if (!defined $title) { $title = $dialog_style_titles[$style]; } my $shell = give $parent -DialogShell, -title => $title; my $dialog; if ($style eq X::Motif::XmDIALOG_CHOICE()) { $dialog = give $shell $type, -dialogType => X::Motif::XmDIALOG_MESSAGE, -message => 'Not implemented'; } else { $dialog = give $shell $type, -dialogType => $style, @options; } foreach ('OK', 'Cancel', 'Help') { if (!defined $show{lc $_}) { my $child = X::Toolkit::search_from_parent($dialog, $_); $child->UnmanageChild() if (defined $child); } } $dialog; } # ================================================================================ # Widget Subresources # # The subresources used by a widget aren't described in the class resource # list, so they have to be added manually. The implementation here requires # the resource type to be pre-registered with the Toolkit. Fortunately, every # type encountered during normal resource registration is remembered so even # custom Motif types should be available. I haven't discovered a portable # way to determine the size of a type used solely as a subresource -- but # hopefully we'll never have to. ' xmTextWidgetClass()->register_subresource('PendingDelete', 'pendingDelete', 'Boolean'); xmTextWidgetClass()->register_subresource('SelectThreshold', 'selectThreshold', 'Int'); xmTextWidgetClass()->register_subresource('BlinkRate', 'blinkRate', 'Int'); xmTextWidgetClass()->register_subresource('Columns', 'columns', 'Short'); xmTextWidgetClass()->register_subresource('CursorPositionVisible', 'cursorPositionVisible', 'Boolean'); xmTextWidgetClass()->register_subresource('FontList', 'fontList', 'FontList'); xmTextWidgetClass()->register_subresource('ResizeHeight', 'resizeHeight', 'Boolean'); xmTextWidgetClass()->register_subresource('ResizeWidth', 'resizeWidth', 'Boolean'); xmTextWidgetClass()->register_subresource('Rows', 'rows', 'Short'); xmTextWidgetClass()->register_subresource('WordWrap', 'wordWrap', 'Boolean'); xmTextWidgetClass()->register_subresource('Scroll', 'scrollHorizontal', 'Boolean'); xmTextWidgetClass()->register_subresource('ScrollSide', 'scrollLeftSide', 'Boolean'); xmTextWidgetClass()->register_subresource('ScrollSide', 'scrollTopSide', 'Boolean'); xmTextWidgetClass()->register_subresource('Scroll', 'scrollVertical', 'Boolean'); # ================================================================================ # Widget Aliases # # Register the widgets under their simple names, e.g. label, button, form -- # this should probably be done as an import statement. xmLabelWidgetClass()->register_alias(-label, 'alignment', XmALIGNMENT_BEGINNING); xmPushButtonWidgetClass()->register_alias(-button); xmToggleButtonWidgetClass()->register_alias(-toggle); xmSeparatorWidgetClass()->register_alias(-separator); xmSeparatorWidgetClass()->register_alias(-spacer, 'separatorType', XmNO_LINE); xmTextWidgetClass()->register_alias(-text); xmTextWidgetClass()->register_alias(-editor); xmTextFieldWidgetClass()->register_alias(-field); xmListWidgetClass()->register_alias(-list); xmFrameWidgetClass()->register_alias(-frame); xmPanedWindowWidgetClass()->register_alias(-pane); xmFormWidgetClass()->register_alias(-form); xmBulletinBoardWidgetClass()->register_alias(-bulletinboard); xmRowColumnWidgetClass()->register_alias(-rowcolumn); xmRowColumnWidgetClass()->register_alias(-menubar, XmNrowColumnType, XmMENU_BAR); xmRowColumnWidgetClass()->register_alias(-menu, \&create_menu); xmRowColumnWidgetClass()->register_alias(-optionmenu, \&create_option_menu); xmRowColumnWidgetClass()->register_alias(-popupmenu, \&create_popup_menu); xmScrolledWindowWidgetClass()->register_alias(-scrolledwindow); xmMessageBoxWidgetClass()->register_alias(-dialog, \&create_dialog); xmDrawingAreaWidgetClass()->register_alias(-drawingarea); xmDrawingAreaWidgetClass()->register_alias(-canvas); xmDialogShellWidgetClass()->register_alias(-dialogshell); xmMenuShellWidgetClass()->register_alias(-menushell); topLevelShellWidgetClass()->register_alias(-toplevel); transientShellWidgetClass()->register_alias(-transient); # ================================================================================ # Motif convenience routines sub generic_XmCreate { my $f = shift; my $type = shift; my $parent = shift; my $name = shift; my %resources = (); my %callbacks; X::Toolkit::Widget::build_strict_resource_table($type, $parent->Class()->name(), \%resources, \%callbacks, @_); my $child = &$f($parent, $name, %resources); if (!defined $child) { carp "couldn't create $type widget $name"; } $child; } sub XmCreateArrowButton { return generic_XmCreate(\&priv_XmCreateArrowButton, 'XmArrowButton', @_); } sub XmCreateBulletinBoard { return generic_XmCreate(\&priv_XmCreateBulletinBoard, 'XmBulletinBoard', @_); } sub XmCreateBulletinBoardDialog { return generic_XmCreate(\&priv_XmCreateBulletinBoardDialog, 'XmMessageBox', @_); } sub XmCreateCascadeButton { return generic_XmCreate(\&priv_XmCreateCascadeButton, 'XmCascadeButton', @_); } sub XmCreateCommand { return generic_XmCreate(\&priv_XmCreateCommand, 'XmCommand', @_); } sub XmCreateCommandDialog { return generic_XmCreate(\&priv_XmCreateCommandDialog, 'XmMessageBox', @_); } sub XmCreateDialogShell { return generic_XmCreate(\&priv_XmCreateDialogShell, 'XmDialogShell', @_); } sub XmCreateDrawingArea { return generic_XmCreate(\&priv_XmCreateDrawingArea, 'XmDrawingArea', @_); } sub XmCreateDrawnButton { return generic_XmCreate(\&priv_XmCreateDrawnButton, 'XmDrawnButton', @_); } sub XmCreateErrorDialog { return generic_XmCreate(\&priv_XmCreateErrorDialog, 'XmMessageBox', @_); } sub XmCreateFileSelectionBox { return generic_XmCreate(\&priv_XmCreateFileSelectionBox, 'XmFileSelectionBox', @_); } sub XmCreateFileSelectionDialog { return generic_XmCreate(\&priv_XmCreateFileSelectionDialog, 'XmMessageBox', @_); } sub XmCreateForm { return generic_XmCreate(\&priv_XmCreateForm, 'XmForm', @_); } sub XmCreateFormDialog { return generic_XmCreate(\&priv_XmCreateFormDialog, 'XmMessageBox', @_); } sub XmCreateFrame { return generic_XmCreate(\&priv_XmCreateFrame, 'XmFrame', @_); } sub XmCreateInformationDialog { return generic_XmCreate(\&priv_XmCreateInformationDialog, 'XmMessageBox', @_); } sub XmCreateLabel { return generic_XmCreate(\&priv_XmCreateLabel, 'XmLabel', @_); } sub XmCreateList { return generic_XmCreate(\&priv_XmCreateList, 'XmList', @_); } sub XmCreateMainWindow { return generic_XmCreate(\&priv_XmCreateMainWindow, 'XmMainWindow', @_); } sub XmCreateMenuBar { return generic_XmCreate(\&priv_XmCreateMenuBar, 'XmMenuBar', @_); } sub XmCreateMenuShell { return generic_XmCreate(\&priv_XmCreateMenuShell, 'XmMenuShell', @_); } sub XmCreateMessageBox { return generic_XmCreate(\&priv_XmCreateMessageBox, 'XmMessageBox', @_); } sub XmCreateMessageDialog { return generic_XmCreate(\&priv_XmCreateMessageDialog, 'XmMessageBox', @_); } sub XmCreateOptionMenu { return generic_XmCreate(\&priv_XmCreateOptionMenu, 'XmOptionMenu', @_); } sub XmCreatePanedWindow { return generic_XmCreate(\&priv_XmCreatePanedWindow, 'XmPanedWindow', @_); } sub XmCreatePopupMenu { return generic_XmCreate(\&priv_XmCreatePopupMenu, 'XmPopupMenu', @_); } sub XmCreatePromptDialog { return generic_XmCreate(\&priv_XmCreatePromptDialog, 'XmMessageBox', @_); } sub XmCreatePulldownMenu { return generic_XmCreate(\&priv_XmCreatePulldownMenu, 'XmPulldownMenu', @_); } sub XmCreatePushButton { return generic_XmCreate(\&priv_XmCreatePushButton, 'XmPushButton', @_); } sub XmCreateQuestionDialog { return generic_XmCreate(\&priv_XmCreateQuestionDialog, 'XmMessageBox', @_); } sub XmCreateRadioBox { return generic_XmCreate(\&priv_XmCreateRadioBox, 'XmRadioBox', @_); } sub XmCreateRowColumn { return generic_XmCreate(\&priv_XmCreateRowColumn, 'XmRowColumn', @_); } sub XmCreateScale { return generic_XmCreate(\&priv_XmCreateScale, 'XmScale', @_); } sub XmCreateScrollBar { return generic_XmCreate(\&priv_XmCreateScrollBar, 'XmScrollBar', @_); } sub XmCreateScrolledList { return generic_XmCreate(\&priv_XmCreateScrolledList, 'XmList', @_); } sub XmCreateScrolledText { return generic_XmCreate(\&priv_XmCreateScrolledText, 'XmText', @_); } sub XmCreateScrolledWindow { return generic_XmCreate(\&priv_XmCreateScrolledWindow, 'XmScrolledWindow', @_); } sub XmCreateSelectionBox { return generic_XmCreate(\&priv_XmCreateSelectionBox, 'XmSelectionBox', @_); } sub XmCreateSelectionDialog { return generic_XmCreate(\&priv_XmCreateSelectionDialog, 'XmMessageBox', @_); } sub XmCreateSeparator { return generic_XmCreate(\&priv_XmCreateSeparator, 'XmSeparator', @_); } sub XmCreateSimpleCheckBox { return generic_XmCreate(\&priv_XmCreateSimpleCheckBox, 'XmSimpleCheckBox', @_); } sub XmCreateSimpleMenuBar { return generic_XmCreate(\&priv_XmCreateSimpleMenuBar, 'XmSimpleMenuBar', @_); } sub XmCreateSimpleOptionMenu { return generic_XmCreate(\&priv_XmCreateSimpleOptionMenu, 'XmSimpleOptionMenu', @_); } sub XmCreateSimplePopupMenu { return generic_XmCreate(\&priv_XmCreateSimplePopupMenu, 'XmSimplePopupMenu', @_); } sub XmCreateSimplePulldownMenu { return generic_XmCreate(\&priv_XmCreateSimplePulldownMenu, 'XmSimplePulldownMenu', @_); } sub XmCreateSimpleRadioBox { return generic_XmCreate(\&priv_XmCreateSimpleRadioBox, 'XmSimpleRadioBox', @_); } sub XmCreateTemplateDialog { return generic_XmCreate(\&priv_XmCreateTemplateDialog, 'XmMessageBox', @_); } sub XmCreateText { return generic_XmCreate(\&priv_XmCreateText, 'XmText', @_); } sub XmCreateTextField { return generic_XmCreate(\&priv_XmCreateTextField, 'XmTextField', @_); } sub XmCreateToggleButton { return generic_XmCreate(\&priv_XmCreateToggleButton, 'XmToggleButton', @_); } sub XmCreateWarningDialog { return generic_XmCreate(\&priv_XmCreateWarningDialog, 'XmMessageBox', @_); } sub XmCreateWorkArea { return generic_XmCreate(\&priv_XmCreateWorkArea, 'XmWorkArea', @_); } sub XmCreateWorkingDialog { return generic_XmCreate(\&priv_XmCreateWorkingDialog, 'XmMessageBox', @_); } # ================================================================================ # Resource converters # # The input to a converter is always a string. The output of a converter # should be a value in the internal resource type, but it can also be a string # that the toolkit or widget set knows how to convert. If a true value is # returned from the converter, then that stops the conversion chain. If # a false (or undefined) value is returned, then conversion will continue # to the registered converter. (Improperly coded converters can break the # rule that the input is always a string!) sub cvt_to_XmLabelType { my $value = shift; if ($$value =~ /string/i) { $$value = XmSTRING } elsif ($$value =~ /pixmap/i) { $$value = XmPIXMAP } } sub cvt_to_HorizontalPosition { my $value = shift; my $widget = shift; if ($$value =~ /^\d+$/i) { $$value = int $$value; } elsif ($$value =~ /^(\d+\.?\d*)(\w*)$/i) { my $x = $1; my $u = $2; if ($u eq 'mm') { $x *= X::Toolkit::width_pixels_per_mm($widget) } if ($u eq 'cm') { $x *= X::Toolkit::width_pixels_per_mm($widget) * 10.0 } elsif ($u eq 'in') { $x *= X::Toolkit::width_pixels_per_mm($widget) * 25.4 } $$value = $x; } } sub cvt_to_VerticalPosition { my $value = shift; my $widget = shift; if ($$value =~ /^\d+$/i) { $$value = int $$value; } elsif ($$value =~ /^(\d+\.?\d*)(\w*)$/i) { my $x = $1; my $u = $2; if ($u eq 'mm') { $x *= X::Toolkit::height_pixels_per_mm($widget) } if ($u eq 'cm') { $x *= X::Toolkit::height_pixels_per_mm($widget) * 10.0 } elsif ($u eq 'in') { $x *= X::Toolkit::height_pixels_per_mm($widget) * 25.4 } $$value = $x; } } sub cvt_to_XmString { my $value = shift; $$value = new X::Motif::String($$value); } sub cvt_to_UserData { my $value = shift; $$value = new X::shared_perl_value($$value); } X::Toolkit::Widget::register_converter('LabelType', \&cvt_to_XmLabelType); X::Toolkit::Widget::register_converter('HorizontalPosition', \&cvt_to_HorizontalPosition); X::Toolkit::Widget::register_converter('VerticalPosition', \&cvt_to_VerticalPosition); X::Toolkit::Widget::register_converter('XmString', \&cvt_to_XmString); # It isn't very satisfying to register a class converter and then # require the resource *type* to be converted. Either class conversion # should be monitored or the forcing/registration scheme should be # re-thought. FIXME X::Toolkit::Widget::conversion_is_mandatory('Pointer'); X::Toolkit::Widget::register_class_converter('UserData', \&cvt_to_UserData); # ================================================================================ # Manager Widget Hooks # # Special routines that handle constraint resources in the standard # Tk-like toolkit api. sub handle_custom_form_constraints { my($res_name, $value, $registry, $resources) = @_; if ($res_name eq 'top' || $res_name eq 'bottom' || $res_name eq 'right' || $res_name eq 'left') { if (ref $value eq 'ARRAY') { X::Toolkit::Widget::set_resource($res_name.'Offset' => $value->[1], $registry, $resources); $value = $value->[0]; } if (ref $value eq 'X::Toolkit::Widget') { X::Toolkit::Widget::set_resource($res_name.'Attachment' => XmATTACH_WIDGET, $registry, $resources); X::Toolkit::Widget::set_resource($res_name.'Widget' => $value, $registry, $resources); } elsif (X::is_integer($value) || $value =~ /^\d+$/) { X::Toolkit::Widget::set_resource($res_name.'Attachment' => XmATTACH_POSITION, $registry, $resources); X::Toolkit::Widget::set_resource($res_name.'Position' => int $value, $registry, $resources); } elsif ($value =~ /^-?form$/i) { X::Toolkit::Widget::set_resource($res_name.'Attachment' => XmATTACH_FORM, $registry, $resources); } elsif ($value =~ /^-?none$/i) { X::Toolkit::Widget::set_resource($res_name.'Attachment' => XmATTACH_NONE, $registry, $resources); } else { carp "value $value not defined for resource $res_name"; return 0; } } elsif ($res_name =~ /^align[-_]?(\w+)/i) { $res_name = lc($1); if ($res_name eq 'top' || $res_name eq 'bottom' || $res_name eq 'right' || $res_name eq 'left') { if (ref $value eq 'ARRAY') { X::Toolkit::Widget::set_resource($res_name.'Offset' => $value->[1], $registry, $resources); $value = $value->[0]; } if (ref $value eq 'X::Toolkit::Widget') { X::Toolkit::Widget::set_resource($res_name.'Attachment' => XmATTACH_OPPOSITE_WIDGET, $registry, $resources); X::Toolkit::Widget::set_resource($res_name.'Widget' => $value, $registry, $resources); } elsif ($value =~ /^-?form$/i) { X::Toolkit::Widget::set_resource($res_name.'Attachment' => XmATTACH_OPPOSITE_FORM, $registry, $resources); } else { carp "value $value must be a widget or form edge for resource align_$res_name"; return 0; } } else { carp "value $value must be a widget to align $res_name"; return 0; } } return 1; } $X::Toolkit::Widget::constraint_handlers{'XmForm'} = \&handle_custom_form_constraints; # ================================================================================ # Callback data structures # This is sort of kludgy right now. There should probably be a generic way to # specify the default callback data structure for a widget. The concatenated # key is used rather than nested hashes because it saves memory. The performance # hit is very minor because lookups are only performed when adding callbacks to # widgets, not when calling them. $X::Toolkit::Widget::call_data_registry{'XmPushButton,activateCallback'} = \"X::Motif::PushButtonCallData"; my $text_verify_call_data = "X::Motif::TextVerifyCallData"; $X::Toolkit::Widget::call_data_registry{'XmTextField,losingFocusCallback'} = \$text_verify_call_data; $X::Toolkit::Widget::call_data_registry{'XmTextField,modifyVerifyCallback'} = \$text_verify_call_data; $X::Toolkit::Widget::call_data_registry{'XmTextField,motionVerifyCallback'} = \$text_verify_call_data; my $list_call_data = "X::Motif::ListCallData"; $X::Toolkit::Widget::call_data_registry{'XmList,singleSelectionCallback'} = \$list_call_data; $X::Toolkit::Widget::call_data_registry{'XmList,multipleSelectionCallback'} = \$list_call_data; $X::Toolkit::Widget::call_data_registry{'XmList,extendedSelectionCallback'} = \$list_call_data; $X::Toolkit::Widget::call_data_registry{'XmList,browseSelectionCallback'} = \$list_call_data; $X::Toolkit::Widget::call_data_registry{'XmList,defaultActionCallback'} = \$list_call_data; package X::Motif::AnyCallData; package X::Motif::ArrowButtonCallData; use vars qw(@ISA); @ISA = qw(X::Motif::AnyCallData); package X::Motif::DrawingAreaCallData; use vars qw(@ISA); @ISA = qw(X::Motif::AnyCallData); package X::Motif::DrawnButtonCallData; use vars qw(@ISA); @ISA = qw(X::Motif::AnyCallData); package X::Motif::PushButtonCallData; use vars qw(@ISA); @ISA = qw(X::Motif::AnyCallData); package X::Motif::RowColumnCallData; use vars qw(@ISA); @ISA = qw(X::Motif::AnyCallData); package X::Motif::ScrollBarCallData; use vars qw(@ISA); @ISA = qw(X::Motif::AnyCallData); package X::Motif::ToggleButtonCallData; use vars qw(@ISA); @ISA = qw(X::Motif::AnyCallData); package X::Motif::ListCallData; use vars qw(@ISA); @ISA = qw(X::Motif::AnyCallData); package X::Motif::SelectionBoxCallData; use vars qw(@ISA); @ISA = qw(X::Motif::AnyCallData); package X::Motif::CommandCallData; use vars qw(@ISA); @ISA = qw(X::Motif::AnyCallData); package X::Motif::FileSelectionCallData; use vars qw(@ISA); @ISA = qw(X::Motif::AnyCallData); package X::Motif::ScaleCallData; use vars qw(@ISA); @ISA = qw(X::Motif::AnyCallData); package X::Motif::TextVerifyCallData; use vars qw(@ISA); @ISA = qw(X::Motif::AnyCallData); package X::Motif::TraverseObscuredCallData; use vars qw(@ISA); @ISA = qw(X::Motif::AnyCallData); # ================================================================================ # Special Toolkit extensions package X::Toolkit::Widget; # The interfaces here are experimental. I'm not sure if they are # useful -- they certainly aren't finished! my %adj = ( 'top' => 'left', 'bottom' => 'left', 'left' => 'top', 'right' => 'top' ); my %opp = ( 'top' => 'bottom', 'bottom' => 'top', 'left' => 'right', 'right' => 'left' ); sub attach_edge_to { my($edge, $widget, $registry, $resources) = @_; if (defined $widget) { set_resource($edge.'Attachment', X::Motif::XmATTACH_WIDGET, $registry, $resources); set_resource($edge.'Widget', $widget, $registry, $resources); } else { set_resource($edge.'Attachment', X::Motif::XmATTACH_FORM, $registry, $resources); } } sub arrange ($;@) { my $self = shift; my $type_name = $self->XtClass()->name(); if ($type_name ne "XmForm") { carp "you can only arrange the widgets in a form widget"; return; } my $fill_x = 0; my $fill_y = 0; my($edge, $adj_edge, $opp_edge, $opp_adj_edge); my %border; my %child = ( ); foreach my $w ($self->XtChildren()) { $child{$w->ID()} = 1; } my $registry = $constraint_resource_registry{$type_name}; my %resources; my($res_name, $value); my $num = scalar @_; my $i = 0; while ($i < $num) { $res_name = $_[$i++]; $res_name =~ s|^-||; $value = $_[$i++]; if ($res_name eq "fill") { $fill_x = ($value =~ /x/i); $fill_y = ($value =~ /y/i); } elsif ($res_name eq 'top' || $res_name eq 'bottom' || $res_name eq 'right' || $res_name eq 'left') { my @peers = (); if (ref $value eq 'X::Toolkit::Widget') { push @peers, $value; } else { @peers = @{$value}; } $edge = $res_name; $adj_edge = $adj{$edge}; $opp_edge = $opp{$edge}; $opp_adj_edge = $opp{$adj_edge}; foreach $value (@peers) { if (!$self->equal($value->XtParent())) { carp "can't pack a widget that isn't in the form"; } elsif (exists $child{$value->ID()}) { delete $child{$value->ID()}; %resources = (); attach_edge_to($edge, $border{$edge}, $registry, \%resources); attach_edge_to($adj_edge, $border{$adj_edge}, $registry, \%resources); if ($edge eq 'top' || $edge eq 'bottom') { if ($fill_x) { attach_edge_to($opp_adj_edge, $border{$opp_adj_edge}, $registry, \%resources); } else { my %sep_resources = (); my $sep = $self->give('Separator', -separatorType => 'no_line'); attach_edge_to($edge, $border{$edge}, $registry, \%sep_resources); attach_edge_to($adj_edge, $value, $registry, \%sep_resources); attach_edge_to($opp_adj_edge, $border{$opp_adj_edge}, $registry, \%sep_resources); $sep->priv_XtSetValues(%sep_resources, $opp_edge.'Attachment' => X::Toolkit::InArg::new('attach_opposite_widget', 'Attachment', 1, 0), $opp_edge.'Widget' => $value); } if (!%child) { if ($fill_y) { attach_edge_to($opp_edge, $border{$opp_edge}, $registry, \%resources); } } } else { if ($fill_y) { attach_edge_to($opp_adj_edge, $border{$opp_adj_edge}, $registry, \%resources); } else { my %sep_resources = (); my $sep = $self->give('Separator', -separatorType => 'no_line'); attach_edge_to($edge, $border{$edge}, $registry, \%sep_resources); attach_edge_to($adj_edge, $value, $registry, \%sep_resources); attach_edge_to($opp_adj_edge, $border{$opp_adj_edge}, $registry, \%sep_resources); $sep->priv_XtSetValues(%sep_resources, $opp_edge.'Attachment' => X::Toolkit::InArg::new('attach_opposite_widget', 'Attachment', 1, 0), $opp_edge.'Widget' => $value); } if (!%child) { if ($fill_x) { attach_edge_to($opp_edge, $border{$opp_edge}, $registry, \%resources); } } } $value->priv_XtSetValues(%resources); $border{$edge} = $value; } } } } } X11::Motif::use_standard_aliases(); 1;