Nekochan Net

Official Chat Channel: #nekochan // irc.nekochan.net
It is currently Tue Jul 29, 2014 2:57 pm

All times are UTC - 8 hours


Forum rules


Any posts concerning pirated software or offering to buy/sell/trade commercial software are subject to removal.



Post new topic Reply to topic  [ 6 posts ] 
Author Message
Unread postPosted: Mon Apr 16, 2012 1:24 pm 
Offline
User avatar

Joined: Tue Oct 12, 2004 2:54 pm
Posts: 288
Location: London, Ingerlund
Hi all....

I was clearing up some old files and came across this text that I put together a million years ago but never 'published'. I suppose this would be a fair read for somebody about to dive into C++/Motif and ViewKit for the first time. Please download the indispensible "ViewKit Programmers Guide" and the two volumes of Motif Reference if you want to make any headway beyond this "Hello World".

Cheers,

Jimmer

----

A "Hello World" for IRIX using Motif and ViewKit.

If you want to write software that looks like it belongs on IRIX, you need Motif and Motif means the complexities of Xt/Xlib. Motif is a crusty old dog of a library containing C-language functions to create and manage a graphical user interface on the X11 display server. It's built on top of another ancient library called Xt, which in turn is built on the low-level core X11 library Xlib. Writing software for this stack of inter-dependent libraries can be complicated.

ViewKit is a library developed by the nice folks at SGI to hide much of the Motif/Xt/Xlib complexity. At the same time it adds some neat Object Oriented Programming (OOP) concepts to make GUI development with Motif significantly less painful. ViewKit was written in C++, the OOP version of C, so if we want to use the library we need to use C++ as our programming language.

As you probably know, C and C++ code needs to be run through a compiler to get an executable program. We could use GCC or MIPSpro, but due to a technical peculiarity of C++ we need to use the same compiler ViewKit was built with. As we're using the IRIX ViewKit which was built with MIPSpro, we'll be using MIPSpro as our C++ compiler.

If you don't have access to MIPSpro you can try and use the ViewKlass clone of ViewKit from viewklass.sourceforge.net. It builds fine using GCC and thus most likely you'll be able to use GCC/ViewKlass for the tutorial. If there's much interest in using ViewKlass and GCC I might write some instructions on how to compile and install it.

Finally, you'll need a text editor. I tend to use nano to edit configuration files and nedit 5.5 for markup and programming. I have no idea why I dont use One Editor To Rule Them All.

1. Text-based "Hello World" in C++.

First let's just do the formalities of a text-based C++ "Hello World". Fire up your editor and type:

Code:
Hello world.


Makes sense to us. A computer however, has no frame of reference so we need to tell it everything it needs to know in order to 'say' hello.

First, we need to put our text into double quotes so that the computer knows it's a single piece of data:

Code:
"Hello World."


Second, we need to have something to put this data into so we can carry it around our program. This something is called a variable. Variables come in different Types, for example: whole numbers like 212 or floating-point numbers like 3.14. In our case we need a C++ variable of type 'string' because our data is alpha-numeric, like so:

Code:
string _bla


Then, after we've declared our shiny new variable to the C++ compiler, we need to assign our data to it, like so:

Code:
_bla = "Hello World."


Finally, we want the computer to 'say' hello. We do this by sending our data-filled variable to your shell 'console' with a C++ function called cout:

Code:
cout << _bla << endl


Note the direction of the arrows and also note how we send endl at the end of the stream too. It's the C++ way to send an 'end of line'.

So the complete program would look like this:

Code:
string _bla
_bla = "Hello World."

cout << _bla << endl


Great. The logic of our program is finished. But we need to sprinkle a bunch of syntactic sugar over our logic for it to become valid C++.

First, there's the fact that in C++ every line of code ends with an semi-colon, like so:

Code:
string _bla ;
_bla = "Hello World." ;

cout << _bla << endl ;


Second, we need to tell the compiler where to find the code for the 'cout' and 'endl' commands and the code for creating 'string' variables. We do this by including this code from a library of files which comes pre-installed with IRIX and MIPSpro, like so:

Code:
#include <string>
#include <iostream>

string _bla;
_bla = "Hello World.";

cout << _bla << endl;


Finally, there's the fact that the compiler always starts compiling from a main function, which we dont have yet. So we need to wrap our code in one, like so:

Code:
#include <string>
#include <iostream>

int main(int argc, char *argv[]){

  string _bla;
  _bla = "Hello World.";
 
  cout << _bla << endl;
}


There's a bit of black magic here, but for now we'll just say that the int before main tells the compiler that it should expect an integer (int) number as a return value from the main function, and argc and argv[] are parameters that are sent into the main function, though we're not using them in our program.

With these modifications we've turned our simple logic into valid C++. Save your C++ code to a file called HelloWorld.C and send it to the C++ the compiler from the shell-prompt, like so:

Code:
CC -O2 -LANG:std HelloWorld.C -o hello


CC being the C++ compiler, -O2 -LANG:std a few compiler options, HelloWorld.C your file with C++ code and -o hello the name of your output file in which to store your compiled program.

After all of this, you can finally run your program:

Code:
./hello


2. Graphical "Hello World" using C++, Motif and ViewKit.

Creating our ViewKit version of "Hello World" is a lot more involved than the text version. We'll need to make a string of text display in a window in a certain font size, we'll need to put that window on the screen, and we'll need to setup a connection to the X11 server so our application can run in the first place.

Like before we'll start from the inside with our string of data, like so:

Code:
"Hello World"


Next, we need a Motif component to show text, which turns out to exist and is called a Label Widget. You'd think we could come up with something like:

Code:
Widget *ui_label = makeAWidget(labelWidgetType, "Hello World");


Unfortunately, this wont work. Motif and Xt provide us with a complicated system of supporting various types of languages and character sets like Chinese and Arabic. Which is nice but it also means there's no 'simple' way of putting a string into a Motif widget. First, we need to use a Motif function to turn our data into something called a Motif Compound String. Like so:

Code:
XmString    _bla;   

_bla = XmStringCreateLocalized("Hello World");


Once we have a compound string, we can feed that into a second function which will create a label for us. Unfortunately, the Xt complications don't end here. Motif makes use of the Xt Resource system. Colour of a thing might be a resource, as might the text of a label, as might height, width and many other things relevant to how our label looks. We need to feed all the relevant Xt resources into our label creation function.

The way to do this is by using an array. This is a data container which can hold a number of items of a particular datatype. For example: a carton with a dozen eggs would be a 2x6 array of type 'egg'. In our case, Xt resources are of type Xarg and in C/C++ we use square brackets define the size of an array. An array called args large enough to hold 10 Xt Resources would be declared like this:

Code:
Xarg   args[10];


The Xt Resource system also requires us to give each Widget in our interface a unique name, so that it can apply new resources to each particular Widget. In our case we might want to use: UILabel.

Finally, the XtCreateWidget() function needs to know how many items are in the Xt resources array we're sending into it. Currently that's just the one label resource so the value is: 1.

If we take all of this into account our 'simple' label making code now looks like this:

Code:
Xarg      args[10];
XmString  _bla;      

_bla = XmStringCreateLocalized("Hello World");

XtSetArg(args[0], XmNlabelString, _bla);

Widget *ui_label = XtCreateWidget("UILabel", xmLabelWidgetClass, args, 1);


With our label code more or less in place, we need to start thinking about putting our label into a window and this is where we get our first taste of using ViewKit and its Object Oriented Programming (OOP) goodness. In OOP a running program is made up of objects that send each other messages requesting activities to be performed or status to be reported. Each new object is created based on a blueprint called a class.

Amongst the many classes that ViewKit provides, the VkSimpleWindow class allows us to easily create a Motif window object and then tell that window object to show itself on the screen, like so:

Code:
VkSimpleWindow *ui_win = new VkSimpleWindow("My First Window");

ui_win->show();


So simple! But how to add our label widget to this window? In order to do this we need to make a sub-class of VkSimpleWindow. A sub-class is a new class which inherits all the properties and the abilities of a 'parent' class, but allows you to add whatever extra properties and abilities you need beyond the ones it inherited. Our VkSimpleWindow has the show() ability for an entire window, but it knows nothing about showing a label. Our sub-class will contain both the basic VkSimpleWindow abilities and the new 'show a label' abilities which we're going to add.

The C++ idiom to define a new HelloWorldWindow class inheriting from the VkSimpleWindow class looks like this:

Code:
class HelloWorldWindow : public VkSimpleWindow {

  public:
      
    HelloWorldWindow(const char *);
    ~HelloWorldWindow();
      
    virtual const char *className();
};


There's a lot of OOP gospel relating to these few lines, but for the moment all you need to know is the following:

HelloWorldWindow(const char *) is a C++ language requirement called a Constructor function. All C++ classes need a constructor, it's a function that gets called automatically when a new object is created.

The className() function is a ViewKit requirement.

Lets continue by filling out the functions of our new HelloWorldWindow class by adding our label code, like so:

Code:
HelloWorldWindow::HelloWorldWindow(const char* name) : VkSimpleWindow(name) {

  int  nr_args;
  Arg  args[10];

  XmString   _string;

  nr_args = 0;
  _string = XmStringCreateLocalized("Hello World");
  XtSetArg(args[nr_args], XmNlabelString, _string); nr_args++;

  Widget ui_label = XtCreateWidget("UILabel", xmLabelWidgetClass, ui_root, args, nr_args);

  XtManageChild(ui_label);

  XmStringFree(_string);
}


HelloWorldWindow::~HelloWorldWindow() { }

const char* HelloWorldWindow::className() { return "HelloWorldWindow"; }


Note how this version of the Xt resource array in our HelloWorldWindow() constructor function uses an extra nr_args variable to sum the number of items we stick into the args array. This is a clever-ish way of keeping track of the number of values used for the XtCreateWidget() function without having to recount by hand each time you add or remove a resource from the array.

Apart from the C++ syntactic sugar, all this does is stick the label creation code into the constructor of the class. In other words: when the window object gets created, the Motif label code is run and a label will be created. However, one essential part of this process is missing: the position of the label in the window.

Positioning widgets in a Motif window is a Black Art performed by using the Motif Layout Manager widgets. In our example we're going to use the Form manager widget. The Form manager works by making the edges of all widgets 'sticky'. You can stick one widget edge to the edge of another widget or even to the form itself.You can also layer Form widgets inside each other to create complex layouts. Think of it as building a brick wall, or if you know about HTML/CSS, as nesting DIVs.

Here's what our code looks like if we add the Motif Form manager widget and the Xt Resources that control it to our code:

Code:
HelloWorldWindow::HelloWorldWindow(const char* name) : VkSimpleWindow(name) {

  int    nr_args;
  Arg    args[10];

  XmString   _string;
 
  nr_args = 0;
  XtSetArg(args[nr_args], XmNwidth, 400); nr_args++;
  XtSetArg(args[nr_args], XmNheight, 300); nr_args++;

  Widget ui_root = XtCreateWidget("UIRoot", xmFormWidgetClass, mainWindowWidget(), args, nr_args);

  nr_args = 0;
  _string = XmStringCreateLocalized("Hello World");
  XtSetArg(args[nr_args], XmNlabelString, _string); nr_args++;
  XtSetArg(args[nr_args], XmNtopAttachment, XmATTACH_FORM); nr_args++;
  XtSetArg(args[nr_args], XmNleftAttachment, XmATTACH_FORM); nr_args++;
  XtSetArg(args[nr_args], XmNrightAttachment, XmATTACH_FORM); nr_args++;
  XtSetArg(args[nr_args], XmNbottomAttachment, XmATTACH_FORM); nr_args++;

  Widget ui_label = XtCreateWidget("UILabel", xmLabelWidgetClass, ui_root, args, nr_args);
 
  XtManageChild(ui_label);

  XmStringFree(_string);

  addView(ui_root);
}


Note how we've added an extra parameter to the XtCreateWidget() functions. For ui_root it's mainWindowWidget() and for ui_label its ui_root. These parameters are essential to positioning widgets - they tell Motif/Xt about the parent/child relationships between the various widgets in the GUI Widget Tree.

There are two ViewKit functions in use here that make all the difference: mainWindowWidget() and addView(). mainWindowWidget() gives you the 'hidden' parent of this Motif window - another Motif/Xt/Xlib complexity made easy. And addView() is a function defined in the VkSimpleWindow class that sets the ui_root Form widget to be the root of this window, making everything else fall into place.

Finally, we need to add our main function so all of this goodness will work:

Code:
int main(int argc, char *argv[]) {

  VkApp *app = new VkApp("HelloWorld", &argc, argv);

  cout << "Hello World Started." << endl;

  HelloWorldWindow *win = new HelloWorldWindow("Hello World Window");   
  win->show();

  app->run();
}


Here we create a VkApp object that connects us to the X11 server and does all the nasty Xt/Xlib initialisation stuff. Good riddance. Then we create a new HelloWorldWindow object and show() it. Then we tell our VkApp object to run() and process any mouse or other events for as long as the application is running. In our case where there isn;t even a button to click that means it'll just wait for a signal from the window manager that you've closed the window. In which case it'll terminate the program.

Because Motif relies on a whole bunch of libraries and the compiler needs to be told about this... it's useful to use a utility program to help compile this code. The program is called make and it reads a Makefile with instructions how to go about compiling various files of code that would make up a complete program.

Below are the Makefile and the complete C++ code with all the includes and an extra bonus element of changing the fontsize of the label of our HelloWorldVk.C program.

Please note that the fontsize thing only works with Motif v2.1 so you need to make sure you've run the symlink script in /usr/Motif-2.1/lib/mksymlinks.

Enjoy!

Jimmer

HelloWorldVk.C

Code:
#include <iostream>
using namespace std;

#include <Vk/VkApp.h>
#include <Vk/VkSimpleWindow.h>

#include <Xm/Form.h>
#include <Xm/Label.h>

class HelloWorldWindow : public VkSimpleWindow {

   public:
      
      HelloWorldWindow(const char *);
      ~HelloWorldWindow();
      
      virtual const char *className();

   private:
   
      static String ui_defaultResources[];
};


String HelloWorldWindow::ui_defaultResources[] = {

   "*renderTable: bold24",
   "*fontType: FONT_IS_FONT",
   "*bold24.fontName: -*-helvetica-bold-r-normal-*-24-*-*-*-*-*-*-*",

   NULL
};


HelloWorldWindow::HelloWorldWindow(const char* name) : VkSimpleWindow(name) {

   int      nr_args;
   Arg      args[10];

   XmString   _string;

   setDefaultResources(mainWindowWidget(), ui_defaultResources);

   nr_args = 0;
   XtSetArg(args[nr_args], XmNwidth, 400); nr_args++;
   XtSetArg(args[nr_args], XmNheight, 300); nr_args++;

   Widget ui_root = XtCreateWidget("UIRoot", xmFormWidgetClass, mainWindowWidget(), args, nr_args);

   nr_args = 0;
   _string = XmStringCreate("Hello World", "bold24");
   XtSetArg(args[nr_args], XmNlabelString, _string); nr_args++;
   XtSetArg(args[nr_args], XmNtopAttachment, XmATTACH_FORM); nr_args++;
   XtSetArg(args[nr_args], XmNleftAttachment, XmATTACH_FORM); nr_args++;
   XtSetArg(args[nr_args], XmNrightAttachment, XmATTACH_FORM); nr_args++;
   XtSetArg(args[nr_args], XmNbottomAttachment, XmATTACH_FORM); nr_args++;

   Widget ui_label = XtCreateWidget("UILabel", xmLabelWidgetClass, ui_root, args, nr_args);

   XtManageChild(ui_label);

   XmStringFree(_string);

   addView(ui_root);
}


HelloWorldWindow::~HelloWorldWindow() {
}


const char* HelloWorldWindow::className() { return "HelloWorldWindow"; }

int main(int argc, char *argv[]) {

   VkApp *app = new VkApp("HelloWorld", &argc, argv);

   cout << "Hello World Started." << endl;

   HelloWorldWindow *win = new HelloWorldWindow("Hello World Window");   
   win->show();

   app->run();
}


Makefile

Code:
APP_NAME = hello

APP_OBJS = HelloWorldVk.o

INCDIRS = -I. -I/usr/local/include  -I/usr/nekoware/include
LIBDIRS   = -L/usr/local/lib -L/usr/nekoware/lib

STDLIBS      = -lm
STDCPPLIBS   = -lC -lCio
X11LIBS    = -lXt -lX11 -lPW
MOTIFLIBS   = -lSgm -lXm
VKLIBS       = -lvk

LIBS=      $(VKLIBS) \
      $(MOTIFLIBS)\
      $(X11LIBS)\
      $(STDCPPLIBS)\
      $(STDLIBS)

CXX      = CC
CXXOPTS    = -O2 -LANG:std

CC      = c99
COPTS      = -O2

all : $(APP_NAME)

$(APP_NAME): ${APP_OBJS}
   ${CXX} ${APP_OBJS} ${LIBDIRS} ${LIBS} -o ${APP_NAME}

%.o : %.c   
   ${CC} ${COPTS} ${INCDIRS} -c $<

%.o : %.C   
   ${CXX} ${CXXOPTS} ${INCDIRS} -c $<


clean :
   rm -rf ${APP_NAME} *.o core *~ ii_*

_________________
:Fuel: redbox 800Mhz 4Gb V12


Top
 Profile  
 
Unread postPosted: Mon Apr 16, 2012 2:06 pm 
Offline
Moderator
Moderator
User avatar

Joined: Sun Jun 06, 2004 4:55 pm
Posts: 5190
Location: NC - USA
Very handy. Thanks for sharing jimmer!

_________________
***********************************************************************
Welcome to ARMLand - 0/0x0d00
running...(sherwood-root 0607201829)
* InfiniteReality/Reality Software, IRIX 6.5 Release *
***********************************************************************


Top
 Profile  
 
Unread postPosted: Mon Apr 16, 2012 2:21 pm 
Offline
User avatar

Joined: Mon Aug 13, 2007 11:13 pm
Posts: 234
Location: Spain
Fantastic!!! Super!!! Thank you!!!

_________________
Alberto
:Fuel: :Octane2: :1600SW: :O2: :O2: :1600SW:
http://www.f3b.es
http://unixverse.blogspot.com


Top
 Profile  
 
Unread postPosted: Mon Apr 16, 2012 2:49 pm 
Offline
User avatar

Joined: Tue Jul 15, 2008 4:48 pm
Posts: 1887
Location: P.O. Box 121, Pymble, Sydney, NSW 2073, Australia.
I have several old books on Xt, Motif, C++ and X11 in general. I think one of the better authors for this topic on IRIX systems is Doug Young.
Doug Young was a SGI employee when he wrote most of his books therefore all the code examples compile on IRIX with MIPS Pro...

I have this and maybe one or two others..
http://www.amazon.com/Object-Oriented-P ... 0136302521
His opus collection:
http://www.amazon.com/Douglas-A-Young/e ... r_dp_pel_1

All of the examples are on various ftp sites here and there.. Look for young in ftp://ftp.x.org/contrib/book_examples/ .... however...
Qt (Qt3 and Qt4 in nekoware) is more modern and more likely to be relevant skills to have for most employers (I'm doing Qt work on gambling equipment, roulette wheels, card decks, dice shakers, etc, The poker machines are completely straight-froward OpenGL).
And with the nekoware Qt versions you can compile and run at least some of the Qt examples from http://www.amazon.com/C-GUI-Programming ... 725&sr=1-3

Xt and Motif ... Neither has much of a pulse but here is another resource: http://www.motifdeveloper.com/code.html

R.

_________________
死の神はりんごだけ食べる

開いた括弧は必ず閉じる -- あるプログラマー

:Tezro: :Tezro: :Onyx2R: :Onyx2RE: :Onyx2: :O3x04R: :O3x0: :O200: :Octane: :Octane2: :O2: :O2: :Indigo2IMP: :PI: :PI: :1600SW: :1600SW: :Indy: :Indy: :Indy: :Indy: :Indy:
:hpserv: J5600, 2 x Mac, 3 x SUN, Alpha DS20E, Alpha 800 5/550, 3 x RS/6000, Amiga 4000 VideoToaster, Amiga4000 -030, 733MHz Sam440 AmigaOS 4.1 update 1.

Sold: :Indy: :Indy: :Indy: :Indigo: Tandem Himalaya S-Series Nonstop S72000 ServerNet.

@PymbleSoftware
Current Apps -> https://itunes.apple.com/au/artist/pymb ... d553990081
Cortex ---> http://www.facebook.com/pages/Cortex-th ... 11?sk=info
Minnie ---> http://www.facebook.com/pages/Minnie-th ... 02?sk=info
Github ---> https://github.com/pymblesoftware
Visit http://www.pymblesoftware.com
Search for "Pymble", "InstaElf", "CryWhy" or "Cricket Score Sheet" in the iPad App store or search for "Pymble" or "CryWhy" in the iPhone App store.


Last edited by PymbleSoftware on Mon Apr 16, 2012 3:04 pm, edited 1 time in total.

Top
 Profile  
 
Unread postPosted: Mon Apr 16, 2012 2:56 pm 
Offline
User avatar

Joined: Tue Oct 12, 2004 2:54 pm
Posts: 288
Location: London, Ingerlund
PymbleSoftware wrote:
I think one of the better authors for this topic on IRIX systems is Doug Young.
Doug Young was a SGI employee when he wrote most of his books therefore all the code examples compile on IRIX with MIPS Pro...


:) Doug Young was the guy behind ViewKit.

PymbleSoftware wrote:
Qt (Qt3 and Qt4 in nekoware) is more modern and more likely to be relevant skills to have for most employers


Of course Qt and Gtk+ and what have you are the obvious choices for fresh development. But from a hobbyist or a nostalgic point of view coding Motif/ViewKit is good fun!

PymbleSoftware wrote:
Xt and Motif ... Neither has much of a pulse.


Both are dead and buried, but if you want to fit in with the old skool IRIX desktop - it's the way to go.

Jimmer.

_________________
:Fuel: redbox 800Mhz 4Gb V12


Top
 Profile  
 
Unread postPosted: Wed Apr 18, 2012 5:03 am 
Offline
User avatar

Joined: Tue Oct 12, 2004 2:54 pm
Posts: 288
Location: London, Ingerlund
Had a second look at HelloWorldVk.C and decided to change it to reflect the fact that if someone would ask me to write a "Hello World" today, I'd use XtVaCreateWidget() instead of messing with the XtCreateWidget() + array of Xargs monkey business. Like so:

Code:
#include <iostream>
using namespace std;

#include <Vk/VkApp.h>
#include <Vk/VkSimpleWindow.h>

#include <Xm/Form.h>
#include <Xm/Label.h>

class HelloWorldWindow : public VkSimpleWindow {

   public:
      
      HelloWorldWindow(const char *);
      ~HelloWorldWindow();
      
      virtual const char *className();

   private:
   
      static String ui_defaultResources[];
};


String HelloWorldWindow::ui_defaultResources[] = {

   "*renderTable: bold24",
   "*fontType: FONT_IS_FONT",
   "*bold24.fontName: -*-helvetica-bold-r-normal-*-24-*-*-*-*-*-*-*",

   NULL
};


HelloWorldWindow::HelloWorldWindow(const char* name) : VkSimpleWindow(name) {

   XmString   _string;

   setDefaultResources(mainWindowWidget(), ui_defaultResources);

   Widget ui_root = XtVaCreateWidget("UIRoot", xmFormWidgetClass, mainWindowWidget(),
   
      XmNwidth, 400,
      XmNheight, 300,
      
      NULL );

   _string = XmStringCreate("Hello World", "bold24");

   Widget ui_label = XtVaCreateWidget("UILabel", xmLabelWidgetClass, ui_root,

      XmNlabelString, _string,

      XmNtopAttachment, XmATTACH_FORM,
      XmNleftAttachment, XmATTACH_FORM,
      XmNrightAttachment, XmATTACH_FORM,
      XmNbottomAttachment, XmATTACH_FORM,

      NULL );

   XtManageChild(ui_label);

   XmStringFree(_string);

   addView(ui_root);
}


HelloWorldWindow::~HelloWorldWindow() {
}


const char* HelloWorldWindow::className() { return "HelloWorldWindow"; }

int main(int argc, char *argv[]) {

   VkApp *app = new VkApp("HelloWorld", &argc, argv);

   cout << "Hello World Started." << endl;

   HelloWorldWindow *win = new HelloWorldWindow("Hello World Window");   
   win->show();

   app->run();
}

_________________
:Fuel: redbox 800Mhz 4Gb V12


Top
 Profile  
 
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 6 posts ] 

All times are UTC - 8 hours


Who is online

Users browsing this forum: No registered users and 1 guest


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot post attachments in this forum

Search for:
Jump to:  
Powered by phpBB® Forum Software © phpBB Group