diff --git a/indra/llui/llurlentry.cpp b/indra/llui/llurlentry.cpp
index c1da73fa839906b128b31bb0516f71355e0977d4..e04ccfbc2f2e293c2c2ee0782045340df2cf4be1 100644
--- a/indra/llui/llurlentry.cpp
+++ b/indra/llui/llurlentry.cpp
@@ -302,10 +302,11 @@ std::string LLUrlEntryAgent::getLabel(const std::string &url, const LLUrlLabelCa
 //
 // LLUrlEntryGroup Describes a Second Life group Url, e.g.,
 // secondlife:///app/group/00005ff3-4044-c79f-9de8-fb28ae0df991/about
+// secondlife:///app/group/00005ff3-4044-c79f-9de8-fb28ae0df991/inspect
 //
 LLUrlEntryGroup::LLUrlEntryGroup()
 {
-	mPattern = boost::regex("secondlife:///app/group/[\\da-f-]+/about",
+	mPattern = boost::regex("secondlife:///app/group/[\\da-f-]+/\\w+",
 							boost::regex::perl|boost::regex::icase);
 	mMenuName = "menu_url_group.xml";
 	mIcon = "Generic_Group";
diff --git a/indra/llui/tests/llurlentry_test.cpp b/indra/llui/tests/llurlentry_test.cpp
index 468fae2ec593268708305b9d484f2e095c2e54ce..128cd134c138b0346de6d45a36d442d471d134f9 100644
--- a/indra/llui/tests/llurlentry_test.cpp
+++ b/indra/llui/tests/llurlentry_test.cpp
@@ -308,6 +308,10 @@ namespace tut
 				  "secondlife:///app/group/00005ff3-4044-c79f-9de8-fb28ae0df991/about",
 				  "secondlife:///app/group/00005ff3-4044-c79f-9de8-fb28ae0df991/about");
 
+		testRegex("Group Url ", r,
+				  "secondlife:///app/group/00005ff3-4044-c79f-9de8-fb28ae0df991/inspect",
+				  "secondlife:///app/group/00005ff3-4044-c79f-9de8-fb28ae0df991/inspect");
+
 		testRegex("Group Url in text", r,
 				  "XXX secondlife:///app/group/00005ff3-4044-c79f-9de8-fb28ae0df991/about XXX",
 				  "secondlife:///app/group/00005ff3-4044-c79f-9de8-fb28ae0df991/about");
diff --git a/indra/newview/CMakeLists.txt b/indra/newview/CMakeLists.txt
index 37acdc3ceff401b94c3f7f2d45dd3aa5f9fe1e1d..c630a56c8dd344880b2cab8061f64efa23aadc8d 100644
--- a/indra/newview/CMakeLists.txt
+++ b/indra/newview/CMakeLists.txt
@@ -240,6 +240,7 @@ set(viewer_SOURCE_FILES
     llimview.cpp
     llimcontrolpanel.cpp
     llinspectavatar.cpp
+    llinspectgroup.cpp
     llinspectobject.cpp
     llinventorybridge.cpp
     llinventoryclipboard.cpp
@@ -709,6 +710,7 @@ set(viewer_HEADER_FILES
     llimview.h
     llimcontrolpanel.h
     llinspectavatar.h
+    llinspectgroup.h
     llinspectobject.h
     llinventorybridge.h
     llinventoryclipboard.h
diff --git a/indra/newview/llfloaterland.cpp b/indra/newview/llfloaterland.cpp
index c1031ee4378d212d003a69d192215e200c7ed889..488d71aa70a27237988009319126108ef32709e3 100644
--- a/indra/newview/llfloaterland.cpp
+++ b/indra/newview/llfloaterland.cpp
@@ -744,6 +744,7 @@ void LLPanelLandGeneral::refreshNames()
 	if (!parcel)
 	{
 		mTextOwner->setText(LLStringUtil::null);
+		mTextGroup->setText(LLStringUtil::null);
 		return;
 	}
 
@@ -764,6 +765,13 @@ void LLPanelLandGeneral::refreshNames()
 	}
 	mTextOwner->setText(owner);
 
+	std::string group;
+	if (!parcel->getGroupID().isNull())
+	{
+		group = LLSLURL::buildCommand("group", parcel->getGroupID(), "inspect");
+	}
+	mTextGroup->setText(group);
+
 	const LLUUID& auth_buyer_id = parcel->getAuthorizedBuyerID();
 	if(auth_buyer_id.notNull())
 	{
@@ -781,20 +789,6 @@ void LLPanelLandGeneral::refreshNames()
 // virtual
 void LLPanelLandGeneral::draw()
 {
-	LLParcel *parcel = mParcel->getParcel();
-	if (parcel)
-	{
-		std::string group;
-		if (!parcel->getGroupID().isNull())
-		{
-			// *TODO: Change to "inspect" when we have group inspectors and
-			// move into refreshNames() above
-			// group = LLSLURL::buildCommand("group", parcel->getGroupID(), "inspect");
-			gCacheName->getGroupName(parcel->getGroupID(), group);
-		}
-		mTextGroup->setText(group);
-	}
-
 	LLPanel::draw();
 }
 
diff --git a/indra/newview/llgroupactions.cpp b/indra/newview/llgroupactions.cpp
index d1cbe96906065f6fd70e5afb978def582b7c931b..220fb3c8a0567ef1790b72c13dff46b022d03ffe 100644
--- a/indra/newview/llgroupactions.cpp
+++ b/indra/newview/llgroupactions.cpp
@@ -35,13 +35,14 @@
 
 #include "llgroupactions.h"
 
+// Viewer includes
 #include "llagent.h"
+#include "llcommandhandler.h"
 #include "llfloaterreg.h"
 #include "llgroupmgr.h"
 #include "llimview.h" // for gIMMgr
 #include "llsidetray.h"
-
-#include "llcommandhandler.h"
+#include "llstatusbar.h"	// can_afford_transaction()
 
 //
 // Globals
@@ -96,6 +97,13 @@ class LLGroupHandler : public LLCommandHandler
 
 			return true;
 		}
+		if (tokens[1].asString() == "inspect")
+		{
+			LLSD key;
+			key["group_id"] = group_id;
+			LLFloaterReg::showInstance("inspect_group", key);
+			return true;
+		}
 		return false;
 	}
 };
@@ -107,6 +115,52 @@ void LLGroupActions::search()
 	LLFloaterReg::showInstance("search", LLSD().insert("panel", "group"));
 }
 
+// static
+void LLGroupActions::join(const LLUUID& group_id)
+{
+	LLGroupMgrGroupData* gdatap = 
+		LLGroupMgr::getInstance()->getGroupData(group_id);
+
+	if (gdatap)
+	{
+		S32 cost = gdatap->mMembershipFee;
+		LLSD args;
+		args["COST"] = llformat("%d", cost);
+		LLSD payload;
+		payload["group_id"] = group_id;
+
+		if (can_afford_transaction(cost))
+		{
+			LLNotifications::instance().add("JoinGroupCanAfford", args, payload, onJoinGroup);
+		}
+		else
+		{
+			LLNotifications::instance().add("JoinGroupCannotAfford", args, payload);
+		}
+	}
+	else
+	{
+		llwarns << "LLGroupMgr::getInstance()->getGroupData(" << group_id 
+			<< ") was NULL" << llendl;
+	}
+}
+
+// static
+bool LLGroupActions::onJoinGroup(const LLSD& notification, const LLSD& response)
+{
+	S32 option = LLNotification::getSelectedOption(notification, response);
+
+	if (option == 1)
+	{
+		// user clicked cancel
+		return false;
+	}
+
+	LLGroupMgr::getInstance()->
+		sendGroupMemberJoin(notification["payload"]["group_id"].asUUID());
+	return false;
+}
+
 // static
 void LLGroupActions::leave(const LLUUID& group_id)
 {
@@ -239,6 +293,14 @@ void LLGroupActions::startChat(const LLUUID& group_id)
 	}
 }
 
+// static
+bool LLGroupActions::isInGroup(const LLUUID& group_id)
+{
+	// *TODO: Move all the LLAgent group stuff into another class, such as
+	// this one.
+	return gAgent.isInGroup(group_id);
+}
+
 // static
 bool LLGroupActions::isAvatarMemberOfGroup(const LLUUID& group_id, const LLUUID& avatar_id)
 {
diff --git a/indra/newview/llgroupactions.h b/indra/newview/llgroupactions.h
index 9fe1da8af21fbae41eda58223cdc34e5fa74d197..74c84d1561749864997d59231dd9f6f3b581abf3 100644
--- a/indra/newview/llgroupactions.h
+++ b/indra/newview/llgroupactions.h
@@ -47,6 +47,9 @@ class LLGroupActions
 	 */
 	static void search();
 
+	/// Join a group.  Assumes LLGroupMgr has data for that group already.
+	static void join(const LLUUID& group_id);
+
 	/**
 	 * Invokes "Leave Group" floater.
 	 */
@@ -87,6 +90,9 @@ class LLGroupActions
 	 */
 	static void startChat(const LLUUID& group_id);
 
+	/// Returns if the current user is a member of the group
+	static bool isInGroup(const LLUUID& group_id);
+
 	/**
 	 * Returns true if avatar is in group.
 	 *
@@ -97,6 +103,7 @@ class LLGroupActions
 	static bool isAvatarMemberOfGroup(const LLUUID& group_id, const LLUUID& avatar_id);
 	
 private:
+	static bool onJoinGroup(const LLSD& notification, const LLSD& response);
 	static bool onLeaveGroup(const LLSD& notification, const LLSD& response);
 };
 
diff --git a/indra/newview/llgroupmgr.cpp b/indra/newview/llgroupmgr.cpp
index 5e50fad0082c21845358309ffc5f2f1a1e9d3114..01d0f2296a6dc81406aae8bc9470dcfb144d7f69 100644
--- a/indra/newview/llgroupmgr.cpp
+++ b/indra/newview/llgroupmgr.cpp
@@ -1325,11 +1325,16 @@ void LLGroupMgr::notifyObservers(LLGroupChange gc)
 {
 	for (group_map_t::iterator gi = mGroups.begin(); gi != mGroups.end(); ++gi)
 	{
+		LLUUID group_id = gi->first;
 		if (gi->second->mChanged)
 		{
+			// Copy the map because observers may remove themselves on update
+			observer_multimap_t observers = mObservers;
+
 			// find all observers for this group id
-			observer_multimap_t::iterator oi = mObservers.find(gi->first);
-			for (; oi != mObservers.end(); ++oi)
+			observer_multimap_t::iterator oi = observers.lower_bound(group_id);
+			observer_multimap_t::iterator end = observers.upper_bound(group_id);
+			for (; oi != end; ++oi)
 			{
 				oi->second->changed(gc);
 			}
diff --git a/indra/newview/llinspectgroup.cpp b/indra/newview/llinspectgroup.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..184d413743cb40ee5bf7e0dd0ebf5d270161ba9e
--- /dev/null
+++ b/indra/newview/llinspectgroup.cpp
@@ -0,0 +1,367 @@
+/** 
+ * @file llinspectgroup.cpp
+ *
+ * $LicenseInfo:firstyear=2009&license=viewergpl$
+ * 
+ * Copyright (c) 2009, Linden Research, Inc.
+ * 
+ * Second Life Viewer Source Code
+ * The source code in this file ("Source Code") is provided by Linden Lab
+ * to you under the terms of the GNU General Public License, version 2.0
+ * ("GPL"), unless you have obtained a separate licensing agreement
+ * ("Other License"), formally executed by you and Linden Lab.  Terms of
+ * the GPL can be found in doc/GPL-license.txt in this distribution, or
+ * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2
+ * 
+ * There are special exceptions to the terms and conditions of the GPL as
+ * it is applied to this Source Code. View the full text of the exception
+ * in the file doc/FLOSS-exception.txt in this software distribution, or
+ * online at
+ * http://secondlifegrid.net/programs/open_source/licensing/flossexception
+ * 
+ * By copying, modifying or distributing this software, you acknowledge
+ * that you have read and understood your obligations described above,
+ * and agree to abide by those obligations.
+ * 
+ * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO
+ * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY,
+ * COMPLETENESS OR PERFORMANCE.
+ * $/LicenseInfo$
+ */
+
+#include "llviewerprecompiledheaders.h"
+
+#include "llinspectgroup.h"
+
+// viewer files
+#include "llgroupactions.h"
+#include "llgroupmgr.h"
+
+// Linden libraries
+#include "llcontrol.h"	// LLCachedControl
+#include "llfloater.h"
+#include "llfloaterreg.h"
+#include "llresmgr.h"	// getMonetaryString()
+#include "lltooltip.h"	// positionViewNearMouse()
+#include "lltrans.h"
+#include "lluictrl.h"
+
+class LLFetchGroupData;
+
+
+//////////////////////////////////////////////////////////////////////////////
+// LLInspectGroup
+//////////////////////////////////////////////////////////////////////////////
+
+/// Group Inspector, a small information window used when clicking
+/// on group names in the 2D UI
+class LLInspectGroup : public LLFloater
+{
+	friend class LLFloaterReg;
+	
+public:
+	// key["group_id"] - Group ID for which to show information
+	// Inspector will be positioned relative to current mouse position
+	LLInspectGroup(const LLSD& key);
+	virtual ~LLInspectGroup();
+	
+	/*virtual*/ void draw();
+	
+	// Because floater is single instance, need to re-parse data on each spawn
+	// (for example, inspector about same group but in different position)
+	/*virtual*/ void onOpen(const LLSD& group_id);
+
+	// When closing they should close their gear menu 
+	/*virtual*/ void onClose(bool app_quitting);
+	
+	// Inspectors close themselves when they lose focus
+	/*virtual*/ void onFocusLost();
+	
+	// Update view based on information from group manager
+	void processGroupData();
+	
+	// Make network requests for all the data to display in this view.
+	// Used on construction and if avatar id changes.
+	void requestUpdate();
+		
+	// Callback for gCacheName to look up group name
+	// Faster than waiting for group properties to return
+	void nameUpdatedCallback(const LLUUID& id,
+							 const std::string& first,
+							 const std::string& last,
+							 BOOL is_group);
+
+	// Button/menu callbacks
+	void onClickViewProfile();
+	void onClickJoin();
+	void onClickLeave();
+	
+private:
+	LLUUID				mGroupID;
+	// an in-flight network request for group properties 
+	// is represented by this object
+	LLFetchGroupData*	mPropertiesRequest;
+	LLFrameTimer		mCloseTimer;
+	LLFrameTimer		mOpenTimer;
+};
+
+//////////////////////////////////////////////////////////////////////////////
+// LLFetchGroupData
+//////////////////////////////////////////////////////////////////////////////
+
+// This object represents a pending request for avatar properties information
+class LLFetchGroupData : public LLGroupMgrObserver
+{
+public:
+	// If the inspector closes it will delete the pending request object, so the
+	// inspector pointer will be valid for the lifetime of this object
+	LLFetchGroupData(const LLUUID& group_id, LLInspectGroup* inspector)
+	:	LLGroupMgrObserver(group_id),
+		mInspector(inspector)
+	{
+		LLGroupMgr* mgr = LLGroupMgr::getInstance();
+		// register ourselves as an observer
+		mgr->addObserver(this);
+		// send a request
+		mgr->sendGroupPropertiesRequest(group_id);
+	}
+	
+	~LLFetchGroupData()
+	{
+		// remove ourselves as an observer
+		LLGroupMgr::getInstance()->removeObserver(this);
+	}
+	
+	void changed(LLGroupChange gc)
+	{
+		if (gc == GC_PROPERTIES)
+		{
+			mInspector->processGroupData();
+		}
+	}
+	
+	LLInspectGroup* mInspector;
+};
+
+LLInspectGroup::LLInspectGroup(const LLSD& sd)
+:	LLFloater( LLSD() ),	// single_instance, doesn't really need key
+	mGroupID(),			// set in onOpen()
+	mPropertiesRequest(NULL),
+	mCloseTimer()
+{
+	mCommitCallbackRegistrar.add("InspectGroup.ViewProfile",
+		boost::bind(&LLInspectGroup::onClickViewProfile, this));
+	mCommitCallbackRegistrar.add("InspectGroup.Join",
+		boost::bind(&LLInspectGroup::onClickJoin, this));	
+	mCommitCallbackRegistrar.add("InspectGroup.Leave",
+		boost::bind(&LLInspectGroup::onClickLeave, this));	
+
+	// can't make the properties request until the widgets are constructed
+	// as it might return immediately, so do it in postBuild.
+}
+
+LLInspectGroup::~LLInspectGroup()
+{
+	// clean up any pending requests so they don't call back into a deleted
+	// view
+	delete mPropertiesRequest;
+	mPropertiesRequest = NULL;
+}
+
+void LLInspectGroup::draw()
+{
+	static LLCachedControl<F32> FADE_TIME(*LLUI::sSettingGroups["config"], "InspectorFadeTime", 1.f);
+	if (mOpenTimer.getStarted())
+	{
+		F32 alpha = clamp_rescale(mOpenTimer.getElapsedTimeF32(), 0.f, FADE_TIME, 0.f, 1.f);
+		LLViewDrawContext context(alpha);
+		LLFloater::draw();
+		if (alpha == 1.f)
+		{
+			mOpenTimer.stop();
+		}
+
+	}
+	else if (mCloseTimer.getStarted())
+	{
+		F32 alpha = clamp_rescale(mCloseTimer.getElapsedTimeF32(), 0.f, FADE_TIME, 1.f, 0.f);
+		LLViewDrawContext context(alpha);
+		LLFloater::draw();
+		if (mCloseTimer.getElapsedTimeF32() > FADE_TIME)
+		{
+			closeFloater(false);
+		}
+	}
+	else
+	{
+		LLFloater::draw();
+	}
+}
+
+
+// Multiple calls to showInstance("inspect_avatar", foo) will provide different
+// LLSD for foo, which we will catch here.
+//virtual
+void LLInspectGroup::onOpen(const LLSD& data)
+{
+	mCloseTimer.stop();
+	mOpenTimer.start();
+
+	mGroupID = data["group_id"];
+
+	// Position the inspector relative to the mouse cursor
+	// Similar to how tooltips are positioned
+	// See LLToolTipMgr::createToolTip
+	if (data.has("pos"))
+	{
+		LLUI::positionViewNearMouse(this, data["pos"]["x"].asInteger(), data["pos"]["y"].asInteger());
+	}
+	else
+	{
+		LLUI::positionViewNearMouse(this);
+	}
+
+	// can't call from constructor as widgets are not built yet
+	requestUpdate();
+}
+
+// virtual
+void LLInspectGroup::onClose(bool app_quitting)
+{  
+}	
+
+//virtual
+void LLInspectGroup::onFocusLost()
+{
+	// Start closing when we lose focus
+	mCloseTimer.start();
+	mOpenTimer.stop();
+}
+
+void LLInspectGroup::requestUpdate()
+{
+	// Don't make network requests when spawning from the debug menu at the
+	// login screen (which is useful to work on the layout).
+	if (mGroupID.isNull())
+	{
+		return;
+	}
+
+	// Clear out old data so it doesn't flash between old and new
+	getChild<LLUICtrl>("group_name")->setValue("");
+	getChild<LLUICtrl>("group_subtitle")->setValue("");
+	getChild<LLUICtrl>("group_details")->setValue("");
+	getChild<LLUICtrl>("group_cost")->setValue("");
+	// Must have a visible button so the inspector can take focus
+	getChild<LLUICtrl>("leave_btn")->setVisible(true);
+	getChild<LLUICtrl>("join_btn")->setVisible(false);
+	
+	// Make a new request for properties
+	delete mPropertiesRequest;
+	mPropertiesRequest = new LLFetchGroupData(mGroupID, this);
+
+	// Name lookup will be faster out of cache, use that
+	gCacheName->get(mGroupID, TRUE,
+		boost::bind(&LLInspectGroup::nameUpdatedCallback,
+			this, _1, _2, _3, _4));
+}
+
+void LLInspectGroup::nameUpdatedCallback(
+	const LLUUID& id,
+	const std::string& first,
+	const std::string& last,
+	BOOL is_group)
+{
+	if (id == mGroupID)
+	{
+		// group names are returned as a first name
+		childSetValue("group_name", LLSD(first) );
+	}
+	
+	// Otherwise possibly a request for an older inspector, ignore it
+}
+
+void LLInspectGroup::processGroupData()
+{
+	LLGroupMgrGroupData* data =
+		LLGroupMgr::getInstance()->getGroupData(mGroupID);
+
+	if (data)
+	{
+		// Noun pluralization depends on language
+		std::string lang = LLUI::getLanguage();
+		std::string members =
+			LLTrans::getCountString(lang, "GroupMembers", data->mMemberCount);
+		getChild<LLUICtrl>("group_subtitle")->setValue( LLSD(members) );
+
+		getChild<LLUICtrl>("group_details")->setValue( LLSD(data->mCharter) );
+
+		getChild<LLUICtrl>("group_icon")->setValue( LLSD(data->mInsigniaID) );
+
+		std::string cost;
+		bool is_member = LLGroupActions::isInGroup(mGroupID);
+		if (is_member)
+		{
+			cost = getString("YouAreMember");
+		}
+		else if (data->mOpenEnrollment)
+		{
+			if (data->mMembershipFee == 0)
+			{
+				cost = getString("FreeToJoin");
+			}
+			else
+			{
+				std::string amount =
+					LLResMgr::getInstance()->getMonetaryString(
+						data->mMembershipFee);
+				LLStringUtil::format_map_t args;
+				args["[AMOUNT]"] = amount;
+				cost = getString("CostToJoin", args);
+			}
+		}
+		else
+		{
+			cost = getString("PrivateGroup");
+		}
+		getChild<LLUICtrl>("group_cost")->setValue(cost);
+
+		getChild<LLUICtrl>("join_btn")->setVisible(!is_member);
+		getChild<LLUICtrl>("leave_btn")->setVisible(is_member);
+
+		// Only enable join button if you are allowed to join
+		bool can_join = !is_member && data->mOpenEnrollment;
+		getChild<LLUICtrl>("join_btn")->setEnabled(can_join);
+	}
+
+	// Delete the request object as it has been satisfied
+	delete mPropertiesRequest;
+	mPropertiesRequest = NULL;
+}
+
+void LLInspectGroup::onClickViewProfile()
+{
+	closeFloater();
+	LLGroupActions::show(mGroupID);
+}
+
+void LLInspectGroup::onClickJoin()
+{
+	closeFloater();
+	LLGroupActions::join(mGroupID);
+}
+
+void LLInspectGroup::onClickLeave()
+{
+	closeFloater();
+	LLGroupActions::leave(mGroupID);
+}
+
+//////////////////////////////////////////////////////////////////////////////
+// LLInspectGroupUtil
+//////////////////////////////////////////////////////////////////////////////
+void LLInspectGroupUtil::registerFloater()
+{
+	LLFloaterReg::add("inspect_group", "inspect_group.xml",
+					  &LLFloaterReg::build<LLInspectGroup>);
+}
diff --git a/indra/newview/llinspectgroup.h b/indra/newview/llinspectgroup.h
new file mode 100644
index 0000000000000000000000000000000000000000..dfd5cbcd55718c7a8d918f57952d943d6b4e4dfd
--- /dev/null
+++ b/indra/newview/llinspectgroup.h
@@ -0,0 +1,41 @@
+/** 
+ * @file llinspectgroup.h
+ *
+ * $LicenseInfo:firstyear=2009&license=viewergpl$
+ * 
+ * Copyright (c) 2009, Linden Research, Inc.
+ * 
+ * Second Life Viewer Source Code
+ * The source code in this file ("Source Code") is provided by Linden Lab
+ * to you under the terms of the GNU General Public License, version 2.0
+ * ("GPL"), unless you have obtained a separate licensing agreement
+ * ("Other License"), formally executed by you and Linden Lab.  Terms of
+ * the GPL can be found in doc/GPL-license.txt in this distribution, or
+ * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2
+ * 
+ * There are special exceptions to the terms and conditions of the GPL as
+ * it is applied to this Source Code. View the full text of the exception
+ * in the file doc/FLOSS-exception.txt in this software distribution, or
+ * online at
+ * http://secondlifegrid.net/programs/open_source/licensing/flossexception
+ * 
+ * By copying, modifying or distributing this software, you acknowledge
+ * that you have read and understood your obligations described above,
+ * and agree to abide by those obligations.
+ * 
+ * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO
+ * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY,
+ * COMPLETENESS OR PERFORMANCE.
+ * $/LicenseInfo$
+ */
+
+#ifndef LLINSPECTGROUP_H
+#define LLINSPECTGROUP_H
+
+namespace LLInspectGroupUtil
+{
+	// Register with LLFloaterReg
+	void registerFloater();
+}
+
+#endif
diff --git a/indra/newview/llpanelgroup.cpp b/indra/newview/llpanelgroup.cpp
index 206d8428be27035963a207831b55d744a4a15f92..f9e93a1d38d5bed1666c6ebcb0ae35fb3844d819 100644
--- a/indra/newview/llpanelgroup.cpp
+++ b/indra/newview/llpanelgroup.cpp
@@ -33,19 +33,22 @@
 
 #include "llpanelgroup.h"
 
+// Library includes
 #include "llbutton.h"
 #include "lltabcontainer.h"
 #include "lltextbox.h"
-#include "llviewermessage.h"
 #include "lluictrlfactory.h"
+
+// Viewer includes
+#include "llviewermessage.h"
 #include "llviewerwindow.h"
 #include "llappviewer.h"
 #include "llnotifications.h"
 #include "llfloaterreg.h"
 #include "llfloater.h"
+#include "llgroupactions.h"
 
 #include "llagent.h" 
-#include "llstatusbar.h"	// can_afford_transaction()
 
 #include "llsidetraypanelcontainer.h"
 
@@ -274,46 +277,11 @@ void LLPanelGroup::onBtnApply(void* user_data)
 	LLPanelGroup* self = static_cast<LLPanelGroup*>(user_data);
 	self->apply();
 }
+
 void LLPanelGroup::onBtnJoin()
 {
 	lldebugs << "joining group: " << mID << llendl;
-
-	LLGroupMgrGroupData* gdatap = LLGroupMgr::getInstance()->getGroupData(mID);
-
-	if (gdatap)
-	{
-		S32 cost = gdatap->mMembershipFee;
-		LLSD args;
-		args["COST"] = llformat("%d", cost);
-		LLSD payload;
-		payload["group_id"] = mID;
-
-		if (can_afford_transaction(cost))
-		{
-			LLNotifications::instance().add("JoinGroupCanAfford", args, payload, LLPanelGroup::joinDlgCB);
-		}
-		else
-		{
-			LLNotifications::instance().add("JoinGroupCannotAfford", args, payload);
-		}
-	}
-	else
-	{
-		llwarns << "LLGroupMgr::getInstance()->getGroupData(" << mID	<< ") was NULL" << llendl;
-	}
-}
-bool LLPanelGroup::joinDlgCB(const LLSD& notification, const LLSD& response)
-{
-	S32 option = LLNotification::getSelectedOption(notification, response);
-
-	if (option == 1)
-	{
-		// user clicked cancel
-		return false;
-	}
-
-	LLGroupMgr::getInstance()->sendGroupMemberJoin(notification["payload"]["group_id"].asUUID());
-	return false;
+	LLGroupActions::join(mID);
 }
 
 void LLPanelGroup::onBtnCancel()
diff --git a/indra/newview/llpanelgroup.h b/indra/newview/llpanelgroup.h
index 628e2389b69f7795ce2eb38612e64653483b8e1f..5c7b0ddd0603bdb162925b0c5b9a13e5df1de386 100644
--- a/indra/newview/llpanelgroup.h
+++ b/indra/newview/llpanelgroup.h
@@ -101,8 +101,6 @@ class LLPanelGroup : public LLPanel,
 	static void onBtnApply(void*);
 	static void onBtnRefresh(void*);
 
-	static bool joinDlgCB(const LLSD& notification, const LLSD& response);
-
 	void reposButton(const std::string& name);
 	void reposButtons();
 	
diff --git a/indra/newview/llselectmgr.cpp b/indra/newview/llselectmgr.cpp
index 5630e8a0e916c953932bfcae3da6395a12fa5a34..a7f0ce16d38611ff8146e20d73bf9b6172b4bbcc 100644
--- a/indra/newview/llselectmgr.cpp
+++ b/indra/newview/llselectmgr.cpp
@@ -2460,8 +2460,7 @@ BOOL LLSelectMgr::selectGetOwner(LLUUID& result_id, std::string& name)
 		BOOL public_owner = (first_id.isNull() && !first_group_owned);
 		if (first_group_owned)
 		{
-			// *TODO: We don't have group inspectors yet
-			name = LLSLURL::buildCommand("group", first_id, "about");
+			name = LLSLURL::buildCommand("group", first_id, "inspect");
 		}
 		else if(!public_owner)
 		{
diff --git a/indra/newview/llviewerfloaterreg.cpp b/indra/newview/llviewerfloaterreg.cpp
index 22081f9efa9780ddc0a6f8d40d44f5a128525b81..26498ffc9bd99bf38544df5e79a6f8234417a9ad 100644
--- a/indra/newview/llviewerfloaterreg.cpp
+++ b/indra/newview/llviewerfloaterreg.cpp
@@ -109,6 +109,7 @@
 #include "llfloaterwindlight.h"
 #include "llfloaterworldmap.h"
 #include "llinspectavatar.h"
+#include "llinspectgroup.h"
 #include "llinspectobject.h"
 #include "llmediaremotectrl.h"
 #include "llmoveview.h"
@@ -172,6 +173,7 @@ void LLViewerFloaterReg::registerFloaters()
 	LLFloaterReg::add("inventory", "floater_inventory.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterInventory>);
 	LLFloaterReg::add("inspect", "floater_inspect.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterInspect>);
 	LLInspectAvatarUtil::registerFloater();
+	LLInspectGroupUtil::registerFloater();
 	LLInspectObjectUtil::registerFloater();
 	
 	LLFloaterReg::add("lagmeter", "floater_lagmeter.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterLagMeter>);
diff --git a/indra/newview/skins/default/xui/en/floater_test_inspectors.xml b/indra/newview/skins/default/xui/en/floater_test_inspectors.xml
index 2011f57d8bf098663779b334686cf2a3bcbadc2e..126bca2074c45a449a3b581eb7becfdf90396259 100644
--- a/indra/newview/skins/default/xui/en/floater_test_inspectors.xml
+++ b/indra/newview/skins/default/xui/en/floater_test_inspectors.xml
@@ -93,7 +93,9 @@
     top_pad="10"
     left_delta="0"
     height="20"
-    width="100"/>
+    width="100"
+ 	  commit_callback.function="ShowGroupInspector"
+	  commit_callback.parameter="" />
   <button
     name="place_btn"
     label="Place"
@@ -112,13 +114,35 @@
   follows="left|top"
   font="SansSerif"
   height="20"
-  layout="topleft"
   left="0"
   max_length="65536"
   name="slurl"
   top_pad="4"
-  width="100">
+  width="150">
     secondlife:///app/agent/00000000-0000-0000-0000-000000000000/inspect
   </text>
+  <text
+  follows="left|top"
+  font="SansSerif"
+  height="20"
+  left="0"
+  max_length="65536"
+  name="slurl_group"
+  top_pad="4"
+  width="150">
+    secondlife:///app/group/00000000-0000-0000-0000-000000000000/inspect
+  </text>
+
+  <text
+  follows="left|top"
+  font="SansSerif"
+  height="20"
+  left="0"
+  max_length="65536"
+  name="slurl_group_about"
+  top_pad="4"
+  width="150">
+    secondlife:///app/group/00000000-0000-0000-0000-000000000000/about
+  </text>
 
 </floater>
diff --git a/indra/newview/skins/default/xui/en/inspect_group.xml b/indra/newview/skins/default/xui/en/inspect_group.xml
new file mode 100644
index 0000000000000000000000000000000000000000..d3f599cbbf9dd8fed48eb47e022d42e403cecda8
--- /dev/null
+++ b/indra/newview/skins/default/xui/en/inspect_group.xml
@@ -0,0 +1,106 @@
+<?xml version="1.0" encoding="utf-8" standalone="yes" ?>
+<!--
+  Not can_close / no title to avoid window chrome
+  Single instance - only have one at a time, recycle it each spawn
+-->
+<floater
+ bevel_style="in"
+ bg_opaque_color="MouseGray"
+ can_close="false"
+ can_minimize="false"
+ height="138"
+ layout="topleft"
+ name="inspect_group"
+ single_instance="true"
+ sound_flags="0"
+ visible="true"
+ width="245">
+  <string name="PrivateGroup">Private group</string>
+  <string name="FreeToJoin">Free to join</string>
+  <string name="CostToJoin">L$[AMOUNT] to join</string>
+  <string name="YouAreMember">You are a member</string>
+  <text
+     follows="all"
+     font="SansSerifLargeBold"
+     height="18"
+     left="8"
+     name="group_name"
+     top="5"
+     text_color="white"
+     use_ellipses="true"
+     width="240"
+     word_wrap="false">
+    Grumpity's Grumpy Group of Moose
+  </text>
+  <text
+   follows="all"
+   font="SansSerifSmallBold"
+   text_color="White"
+   height="18"
+   left="8"
+   name="group_subtitle"
+   use_ellipses="true"
+   top_pad="0"
+   width="170">
+    123 members
+  </text>
+  <text
+   follows="all"
+   height="50"
+   left="8"
+   name="group_details"
+   top_pad="0"
+   width="170"
+   word_wrap="true">
+A group of folks charged with creating a room with a moose.
+Fear the moose!  Fear it!  And the mongoose too!
+  </text>
+  <text
+   follows="all"
+   height="15"
+   left="8"
+   name="group_cost"
+   top_pad="2"
+   width="170">
+L$123 to join
+  </text>
+  <icon
+     follows="all"
+     height="38"
+     right="-25"
+     mouse_opaque="true"
+     name="group_icon"
+     top="24"
+     width="38" />
+    <button
+     follows="top|left"
+     height="18"
+     image_disabled="ForwardArrow_Disabled"
+     image_selected="ForwardArrow_Press"
+     image_unselected="ForwardArrow_Off"
+     name="view_profile_btn"
+     picture_style="true"
+     right="-8"
+     top="35"
+     left_delta="110"
+     tab_stop="false"
+     width="18" />
+  <button
+   follows="bottom|left"
+   height="23"
+   label="Join"
+   left="8"
+   top="246"
+   name="join_btn"
+   width="100"
+   commit_callback.function="InspectGroup.Join"/>
+  <button
+   follows="bottom|left"
+   height="23"
+   label="Leave"
+   left="8"
+   top="246"
+   name="leave_btn"
+   width="100"
+   commit_callback.function="InspectGroup.Leave"/>
+</floater>
diff --git a/indra/newview/skins/default/xui/en/strings.xml b/indra/newview/skins/default/xui/en/strings.xml
index 2a616d8e2f7ff990e63f6d22c5dddc601575f74e..d13f5dbde309a35b4f2475576960c4389a9aeb02 100644
--- a/indra/newview/skins/default/xui/en/strings.xml
+++ b/indra/newview/skins/default/xui/en/strings.xml
@@ -1951,6 +1951,10 @@ this texture in your inventory
   <string name="AgeDaysA">[COUNT] day</string>
   <string name="AgeDaysB">[COUNT] days</string>
   <string name="AgeDaysC">[COUNT] days</string>
+  <!-- Group member counts -->
+  <string name="GroupMembersA">[COUNT] member</string>
+  <string name="GroupMembersB">[COUNT] members</string>
+  <string name="GroupMembersC">[COUNT] members</string>
 
   <!-- Account types, see LLAvatarPropertiesProcessor -->
   <string name="AcctTypeResident">Resident</string>