diff --git a/src/preferences/Jamfile b/src/preferences/Jamfile index 95b8c7b278..34253ff322 100644 --- a/src/preferences/Jamfile +++ b/src/preferences/Jamfile @@ -21,6 +21,7 @@ SubInclude HAIKU_TOP src preferences opengl ; SubInclude HAIKU_TOP src preferences print ; SubInclude HAIKU_TOP src preferences screen ; SubInclude HAIKU_TOP src preferences screensaver ; +SubInclude HAIKU_TOP src preferences shortcuts ; SubInclude HAIKU_TOP src preferences sounds ; SubInclude HAIKU_TOP src preferences time ; SubInclude HAIKU_TOP src preferences touchpad ; diff --git a/src/preferences/shortcuts/Jamfile b/src/preferences/shortcuts/Jamfile new file mode 100644 index 0000000000..02bb103f49 --- /dev/null +++ b/src/preferences/shortcuts/Jamfile @@ -0,0 +1,25 @@ +SubDir HAIKU_TOP src preferences shortcuts ; + +SEARCH_SOURCE += [ FDirName $(HAIKU_TOP) src preferences shortcuts clv ] ; +SEARCH_SOURCE += [ FDirName $(HAIKU_TOP) src add-ons input_server filters shortcut_catcher ] ; + +Preference Shortcuts : + main.cpp + MetaKeyStateMap.cpp + ResizableButton.cpp + ShortcutsApp.cpp + ShortcutsSpec.cpp + ShortcutsWindow.cpp + +# clv files + CLVColumn.cpp + CLVColumnLabelView.cpp + CLVListItem.cpp + ColumnListView.cpp + MouseWatcher.cpp + PrefilledBitmap.cpp + ScrollViewCorner.cpp + + : be tracker libshortcuts_shared.a $(TARGET_LIBSTDC++) + : Shortcuts.rsrc +; diff --git a/src/preferences/shortcuts/MetaKeyStateMap.cpp b/src/preferences/shortcuts/MetaKeyStateMap.cpp new file mode 100644 index 0000000000..15180e9389 --- /dev/null +++ b/src/preferences/shortcuts/MetaKeyStateMap.cpp @@ -0,0 +1,91 @@ +/* + * Copyright 1999-2009 Haiku Inc. All rights reserved. + * Distributed under the terms of the MIT License. + * + * Authors: + * Jeremy Friesner + */ + + +#include "MetaKeyStateMap.h" + + +#include +#include + + +#include "BitFieldTesters.h" + + +MetaKeyStateMap::MetaKeyStateMap() + : + fKeyName(NULL) +{ + // User MUST call SetInfo() before using! +} + + +MetaKeyStateMap::MetaKeyStateMap(const char* name) +{ + SetInfo(name); +} + + +void +MetaKeyStateMap::SetInfo(const char* keyName) +{ + fKeyName = new char[strlen(keyName) + 1]; + strcpy(fKeyName, keyName); +} + + +MetaKeyStateMap::~MetaKeyStateMap() +{ + delete [] fKeyName; + int nr = fStateDescs.CountItems(); + for (int i = 0; i < nr; i++) + delete [] ((const char*) fStateDescs.ItemAt(i)); + + nr = fStateTesters.CountItems(); + for (int j = 0; j < nr; j++) + delete ((BitFieldTester*) fStateTesters.ItemAt(j)); + // _stateBits are stored in-line, no need to delete them +} + + +void +MetaKeyStateMap::AddState(const char* d, const BitFieldTester* t) +{ + char* copy = new char[strlen(d) + 1]; + strcpy(copy, d); + fStateDescs.AddItem(copy); + fStateTesters.AddItem((void *)t); +} + + +int +MetaKeyStateMap::GetNumStates() const +{ + return fStateTesters.CountItems(); +} + + +const BitFieldTester* +MetaKeyStateMap::GetNthStateTester(int stateNum) const +{ + return ((const BitFieldTester*) fStateTesters.ItemAt(stateNum)); +} + + +const char* +MetaKeyStateMap::GetNthStateDesc(int stateNum) const +{ + return ((const char*) fStateDescs.ItemAt(stateNum)); +} + + +const char* +MetaKeyStateMap::GetName() const +{ + return fKeyName; +} diff --git a/src/preferences/shortcuts/MetaKeyStateMap.h b/src/preferences/shortcuts/MetaKeyStateMap.h new file mode 100644 index 0000000000..a119c7920a --- /dev/null +++ b/src/preferences/shortcuts/MetaKeyStateMap.h @@ -0,0 +1,66 @@ +/* + * Copyright 1999-2009 Haiku Inc. All rights reserved. + * Distributed under the terms of the MIT License. + * + * Authors: + * Jeremy Friesner + */ + + +#ifndef MetaKeyStateMap_h +#define MetaKeyStateMap_h + +#include +#include + +class BitFieldTester; + +// This class defines a set of possible chording states (e.g. "Left only", +// "Right only", "Both", "Either") for a meta-key (e.g. Shift), and the +// description strings and qualifier bit-chords that go with them. +class MetaKeyStateMap { +public: + + // Note: You MUST call SetInfo() directly after using this ctor! + MetaKeyStateMap(); + + // Creates a MetaKeyStateMap with the give name + // (e.g. "Shift" or "Ctrl") + MetaKeyStateMap(const char* keyName); + + + ~MetaKeyStateMap(); + + // For when you have to use the default ctor + void SetInfo(const char* keyName); + + // (tester) becomes property of this map! + void AddState(const char* desc, const BitFieldTester* tester); + + // Returns the name of the meta-key (e.g. "Ctrl") + const char* GetName() const; + + // Returns the number of possible states contained in this + // MetaKeyStateMap. + int GetNumStates() const; + + // Returns a BitFieldTester that tests for the nth state's + // presence. + const BitFieldTester* GetNthStateTester(int stateNum) const; + + // Returns a textual description of the nth state (e.g. "Left") + const char* GetNthStateDesc(int stateNum) const; + +private: + // e.g. "Alt" or "Ctrl" + char* fKeyName; + + // list of strings e.g. "Left" or "Both" + BList fStateDescs; + + // list of BitFieldTesters for testing bits of modifiers + // in state + BList fStateTesters; +}; + +#endif diff --git a/src/preferences/shortcuts/ResizableButton.cpp b/src/preferences/shortcuts/ResizableButton.cpp new file mode 100644 index 0000000000..daf6b12d02 --- /dev/null +++ b/src/preferences/shortcuts/ResizableButton.cpp @@ -0,0 +1,35 @@ +/* + * Copyright 1999-2009 Haiku Inc. All rights reserved. + * Distributed under the terms of the MIT License. + * + * Authors: + * Jeremy Friesner + */ + + +#include "ResizableButton.h" + +ResizableButton::ResizableButton(BRect parentFrame, BRect frame, + const char* name, const char* label, BMessage* msg) + : + BButton(frame, name, label, msg, B_FOLLOW_BOTTOM) +{ + float width = parentFrame.right - parentFrame.left; + float height = parentFrame.bottom - parentFrame.top; + fPercentages.left = frame.left / width; + fPercentages.top = frame.top / height; + fPercentages.right = frame.right / width; + fPercentages.bottom = frame.bottom / height; +} + + +void +ResizableButton::ChangeToNewSize(float newWidth, float newHeight) +{ + float newX = fPercentages.left* newWidth; + float newW = (fPercentages.right* newWidth) - newX; + BRect b = Frame(); + MoveBy(newX - b.left, 0); + ResizeTo(newW, b.bottom - b.top); + Invalidate(); +} diff --git a/src/preferences/shortcuts/ResizableButton.h b/src/preferences/shortcuts/ResizableButton.h new file mode 100644 index 0000000000..81fc4bcd4d --- /dev/null +++ b/src/preferences/shortcuts/ResizableButton.h @@ -0,0 +1,30 @@ +/* + * Copyright 1999-2009 Haiku Inc. All rights reserved. + * Distributed under the terms of the MIT License. + * + * Authors: + * Jeremy Friesner + */ + + +#ifndef ResizableButton_h +#define ResizableButton_h + +#include +#include +#include +#include + +//Just like a regular BButton, but with a handy resize method added +class ResizableButton : public BButton { +public: + ResizableButton(BRect parentFrame, BRect frame, + const char* name, const char* label, + BMessage* msg); + + virtual void ChangeToNewSize(float newWidth, float newHeight); +private: + BRect fPercentages; +}; + +#endif diff --git a/src/preferences/shortcuts/Shortcuts.rsrc b/src/preferences/shortcuts/Shortcuts.rsrc new file mode 100644 index 0000000000..c88b5bc796 Binary files /dev/null and b/src/preferences/shortcuts/Shortcuts.rsrc differ diff --git a/src/preferences/shortcuts/ShortcutsApp.cpp b/src/preferences/shortcuts/ShortcutsApp.cpp new file mode 100644 index 0000000000..9146911954 --- /dev/null +++ b/src/preferences/shortcuts/ShortcutsApp.cpp @@ -0,0 +1,48 @@ +/* + * Copyright 1999-2009 Haiku Inc. All rights reserved. + * Distributed under the terms of the MIT License. + * + * Authors: + * Jeremy Friesner + * Fredrik Modéen + */ + +#include "ShortcutsApp.h" + + +#include "Alert.h" + + +#include "ShortcutsWindow.h" + +#define APPLICATION_SIGNATURE "application/x-vnd.ShortcutsKeys" + +ShortcutsApp::ShortcutsApp() + : + BApplication(APPLICATION_SIGNATURE) +{ +} + + +void +ShortcutsApp::ReadyToRun() +{ + ShortcutsWindow* window = new ShortcutsWindow(); + window->Show(); +} + + +ShortcutsApp::~ShortcutsApp() +{ + +} + + +void +ShortcutsApp::AboutRequested() +{ + BAlert* alert = new BAlert("About Shortcuts", + "Shortcuts v1.28(SpicyKeys v1.28)\nby Jeremy Friesner" + , "Ok"); + alert->Go(); +} diff --git a/src/preferences/shortcuts/ShortcutsApp.h b/src/preferences/shortcuts/ShortcutsApp.h new file mode 100644 index 0000000000..9c6e63e8c8 --- /dev/null +++ b/src/preferences/shortcuts/ShortcutsApp.h @@ -0,0 +1,24 @@ +/* + * Copyright 1999-2009 Haiku Inc. All rights reserved. + * Distributed under the terms of the MIT License. + * + * Authors: + * Jeremy Friesner + * Fredrik Modéen + */ + + +#ifndef ShortcutsApp_h +#define ShortcutsApp_h + +#include + +class ShortcutsApp : public BApplication { +public: + ShortcutsApp(); + ~ShortcutsApp(); + virtual void ReadyToRun(); + virtual void AboutRequested(); +}; + +#endif diff --git a/src/preferences/shortcuts/ShortcutsSpec.cpp b/src/preferences/shortcuts/ShortcutsSpec.cpp new file mode 100644 index 0000000000..4ec72fbd2d --- /dev/null +++ b/src/preferences/shortcuts/ShortcutsSpec.cpp @@ -0,0 +1,838 @@ +/* + * Copyright 1999-2009 Haiku Inc. All rights reserved. + * Distributed under the terms of the MIT License. + * + * Authors: + * Jeremy Friesner + */ + + +#include "ShortcutsSpec.h" + + +#include +#include + +#include +#include +#include +#include +#include +#include + + +#include "ColumnListView.h" + + +#include "Colors.h" +#include "MetaKeyStateMap.h" +#include "BitFieldTesters.h" +#include "CommandActuators.h" +#include "ParseCommandLine.h" + +#define CLASS "ShortcutsSpec : " + +const float _height = 20.0f; + +static MetaKeyStateMap _metaMaps[ShortcutsSpec::NUM_META_COLUMNS]; +static bool _metaMapsInitialized = false; + +static bool _fontCached = false; +static BFont _viewFont; +static float _fontHeight; +static BBitmap * _actuatorBitmaps[2]; + +// These meta-keys are pretty standard +#define SHIFT_NAME "Shift" +#define CONTROL_NAME "Control" + +// These meta-keys have different names on an Intel keyboard +#if __INTEL__ + #define OPTION_NAME "Window" + #define COMMAND_NAME "Alt" +#else + #define OPTION_NAME "Option" + #define COMMAND_NAME "Command" +#endif + +#define ICON_BITMAP_RECT BRect(0.0f, 0.0f, 15.0f, 15.0f) +#define ICON_BITMAP_SPACE B_COLOR_8_BIT + +// Returns the (pos)'th char in the string, or '\0' if (pos) if off the end of +// the string +static char GetLetterAt(const char* str, int pos); + +static char +GetLetterAt(const char* str, int pos) +{ + for (int i = 0; i < pos; i++) + if (str[i] == '\0') + return '\0'; + return str[pos]; +} + + +// Setup the states in a standard manner for a pair of meta-keys. +static void +SetupStandardMap(MetaKeyStateMap& map, const char* name, uint32 both, + uint32 left, uint32 right) +{ + map.SetInfo(name); + + // In this state, neither key may be pressed. + map.AddState("(None)", new HasBitsFieldTester(0, both)); + + // Here, either may be pressed. (Remember both is NOT a 2-bit chord, it's + // another bit entirely) + map.AddState("Either", new HasBitsFieldTester(both)); + + // Here, only the left may be pressed + map.AddState("Left", new HasBitsFieldTester(left, right)); + + // Here, only the right may be pressed + map.AddState("Right", new HasBitsFieldTester(right, left)); + + // Here, both must be pressed. + map.AddState("Both", new HasBitsFieldTester(left | right)); +} + + +MetaKeyStateMap & GetNthKeyMap(int which) +{ + return _metaMaps[which]; +} + + +static BBitmap* MakeActuatorBitmap(bool lit); +static BBitmap* +MakeActuatorBitmap(bool lit) +{ + BBitmap* map = new BBitmap(ICON_BITMAP_RECT, ICON_BITMAP_SPACE, true); + const rgb_color yellow = {255, 255, 0}; + const rgb_color red = {200, 200, 200}; + const rgb_color black = {0, 0, 0}; + const BPoint points[10] = { + BPoint(8, 0), BPoint(9.8, 5.8), BPoint(16, 5.8), + BPoint(11, 9.0), BPoint(13, 16), BPoint(8, 11), + BPoint(3, 16), BPoint(5, 9.0), BPoint(0, 5.8), + BPoint(6.2, 5.8) }; + + BView* view = new BView(BRect(0, 0, 16, 16), NULL, B_FOLLOW_ALL_SIDES, 0L); + map->AddChild(view); + map->Lock(); + view->SetHighColor(B_TRANSPARENT_32_BIT); + view->FillRect(ICON_BITMAP_RECT); + view->SetHighColor(lit ? yellow : red); + view->FillPolygon(points, 10); + view->SetHighColor(black); + view->StrokePolygon(points, 10); + map->Unlock(); + map->RemoveChild(view); + delete view; + return map; +} + + +void InitializeMetaMaps() +{ + _metaMapsInitialized = true; + SetupStandardMap(_metaMaps[ShortcutsSpec::SHIFT_COLUMN_INDEX], SHIFT_NAME, + B_SHIFT_KEY, B_LEFT_SHIFT_KEY, B_RIGHT_SHIFT_KEY); + + SetupStandardMap(_metaMaps[ShortcutsSpec::CONTROL_COLUMN_INDEX], + CONTROL_NAME, B_CONTROL_KEY, B_LEFT_CONTROL_KEY, B_RIGHT_CONTROL_KEY); + + SetupStandardMap(_metaMaps[ShortcutsSpec::COMMAND_COLUMN_INDEX], + COMMAND_NAME, B_COMMAND_KEY, B_LEFT_COMMAND_KEY, B_RIGHT_COMMAND_KEY); + + SetupStandardMap(_metaMaps[ShortcutsSpec::OPTION_COLUMN_INDEX], OPTION_NAME + , B_OPTION_KEY, B_LEFT_OPTION_KEY, B_RIGHT_OPTION_KEY); + + _actuatorBitmaps[0] = MakeActuatorBitmap(false); + _actuatorBitmaps[1] = MakeActuatorBitmap(true); +} + + +ShortcutsSpec::ShortcutsSpec(const char* cmd) + : + CLVListItem(0, false, false, _height), + fCommand(NULL), + fTextOffset(0), + fBitmap(ICON_BITMAP_RECT, ICON_BITMAP_SPACE), + fLastBitmapName(NULL), + fBitmapValid(false), + fKey(0), + fCursorPtsValid(false) +{ + for (int i = 0; i < NUM_META_COLUMNS; i++) + fMetaCellStateIndex[i] = 0; + SetCommand(cmd); +} + + +ShortcutsSpec::ShortcutsSpec(const ShortcutsSpec& from) + : + CLVListItem(0, false, false, _height), + fCommand(NULL), + fTextOffset(from.fTextOffset), + fBitmap(ICON_BITMAP_RECT, ICON_BITMAP_SPACE), + fLastBitmapName(NULL), + fBitmapValid(false), + fKey(from.fKey), + fCursorPtsValid(false) +{ + for (int i = 0; i < NUM_META_COLUMNS; i++) + fMetaCellStateIndex[i] = from.fMetaCellStateIndex[i]; + + SetCommand(from.fCommand); + SetSelectedColumn(from.GetSelectedColumn()); +} + + +ShortcutsSpec::ShortcutsSpec(BMessage* from) + : + CLVListItem(0, false, false, _height), + fCommand(NULL), + fTextOffset(0), + fBitmap(ICON_BITMAP_RECT, ICON_BITMAP_SPACE), + fLastBitmapName(NULL), + fBitmapValid(false), + fCursorPtsValid(false) +{ + const char* temp; + if (from->FindString("command", &temp) != B_NO_ERROR) { + printf(CLASS " Error, no command string in archive BMessage!\n"); + temp = ""; + } + + SetCommand(temp); + + if (from->FindInt32("key", (int32*) &fKey) != B_NO_ERROR) + printf(CLASS " Error, no key int32 in archive BMessage!\n"); + + for (int i = 0; i < NUM_META_COLUMNS; i++) + if (from->FindInt32("mcidx", i, (int32*)&fMetaCellStateIndex[i]) + != B_NO_ERROR) + printf(CLASS " Error, no modifiers int32 in archive BMessage!\n"); +} + + +void +ShortcutsSpec::SetCommand(const char* command) +{ + delete[] fCommand; // out with the old (if any)... + fCommandLen = strlen(command) + 1; + fCommandNul = fCommandLen - 1; + fCommand = new char[fCommandLen]; + strcpy(fCommand, command); + _UpdateIconBitmap(); +} + + +const char* +ShortcutsSpec::GetColumnName(int i) +{ + return _metaMaps[i].GetName(); +} + + +status_t +ShortcutsSpec::Archive(BMessage* into, bool deep) const +{ + status_t ret = BArchivable::Archive(into, deep); + if (ret != B_NO_ERROR) + return ret; + + into->AddString("class", "ShortcutsSpec"); + + // These fields are for our prefs panel's benefit only + into->AddString("command", fCommand); + into->AddInt32("key", fKey); + + // Assemble a BitFieldTester for the input_server add-on to use... + MinMatchFieldTester test(NUM_META_COLUMNS, false); + for (int i = 0; i < NUM_META_COLUMNS; i++) { + // for easy parsing by prefs applet on load-in + into->AddInt32("mcidx", fMetaCellStateIndex[i]); + test.AddSlave(_metaMaps[i].GetNthStateTester(fMetaCellStateIndex[i])); + } + + BMessage testerMsg; + ret = test.Archive(&testerMsg); + if (ret != B_NO_ERROR) + return ret; + + into->AddMessage("modtester", &testerMsg); + + // And also create a CommandActuator for the input_server add-on to execute + CommandActuator* act = CreateCommandActuator(fCommand); + BMessage actMsg; + ret = act->Archive(&actMsg); + if (ret != B_NO_ERROR) + return ret; + delete act; + + into->AddMessage("act", &actMsg); + return ret; +} + + +static bool IsValidActuatorName(const char* c); +static bool +IsValidActuatorName(const char* c) +{ + return ((strcmp(c, "InsertString") == 0) || + (strcmp(c, "MoveMouse") == 0) || + (strcmp(c, "MoveMouseTo") == 0) || + (strcmp(c, "MouseButton") == 0) || + (strcmp(c, "LaunchHandler") == 0) || // new for v1.21 --jaf + (strcmp(c, "Multi") == 0) || // new for v1.24 --jaf + (strcmp(c, "MouseDown") == 0) || // new for v1.24 --jaf + (strcmp(c, "MouseUp") == 0) || // new for v1.24 --jaf + (strcmp(c, "SendMessage") == 0) || // new for v1.25 --jaf + (strcmp(c, "Beep") == 0)); +} + + +BArchivable* +ShortcutsSpec::Instantiate(BMessage* from) +{ + bool validateOK = false; + if (validate_instantiation(from, "ShortcutsSpec")) + validateOK = true; + else //test the old one. + if (validate_instantiation(from, "SpicyKeysSpec")) + validateOK = true; + + if (!validateOK) + return NULL; + + return new ShortcutsSpec(from); +} + + +ShortcutsSpec::~ShortcutsSpec() +{ + delete[] fCommand; + delete[] fLastBitmapName; +} + + +void +ShortcutsSpec::_CacheViewFont(BView* owner) +{ + if (_fontCached == false) { + _fontCached = true; + owner->GetFont(&_viewFont); + font_height fh; + _viewFont.GetHeight(&fh); + _fontHeight = fh.ascent - fh.descent; + } +} + + +void +ShortcutsSpec::DrawItemColumn(BView* owner, BRect item_column_rect, + int32 column_index, bool columnSelected, bool complete) +{ + const float STRING_COLUMN_LEFT_MARGIN = 25.0f; // 16 for the icon,+9 empty + + rgb_color color; + bool selected = IsSelected(); + if (selected) + color = columnSelected ? BeBackgroundGrey : BeListSelectGrey; + else + color = BeInactiveControlGrey; + owner->SetLowColor(color); + owner->SetDrawingMode(B_OP_COPY); + owner->SetHighColor(color); + owner->FillRect(item_column_rect); + + const char* text = GetCellText(column_index); + + if (text == NULL) + return; + + float textWidth = _viewFont.StringWidth(text); + BPoint point; + rgb_color lowColor = color; + + if (column_index == STRING_COLUMN_INDEX) { + // left justified + point.Set(item_column_rect.left + STRING_COLUMN_LEFT_MARGIN, + item_column_rect.top + fTextOffset); + + item_column_rect.left = point.x;//keep text from drawing into icon area + + // scroll if too wide + float rectWidth = item_column_rect.Width() - STRING_COLUMN_LEFT_MARGIN; + float extra = textWidth - rectWidth; + if (extra > 0.0f) + point.x -= extra; + } else { + if ((column_index < NUM_META_COLUMNS) && (text[0] == '(')) + return; // don't draw for this ... + + if ((column_index <= NUM_META_COLUMNS) && (text[0] == '\0')) + return; // don't draw for this ... + + // centered + point.Set((item_column_rect.left + item_column_rect.right) / 2.0, + item_column_rect.top + fTextOffset); + _CacheViewFont(owner); + point.x -= textWidth / 2.0f; + } + + BRegion Region; + Region.Include(item_column_rect); + owner->ConstrainClippingRegion(&Region); + if (column_index != STRING_COLUMN_INDEX) { + const float KEY_MARGIN = 3.0f; + const float CORNER_RADIUS = 3.0f; + _CacheViewFont(owner); + + // How about I draw a nice "key" background for this one? + BRect textRect(point.x - KEY_MARGIN, (point.y-_fontHeight) - KEY_MARGIN + , point.x + textWidth + KEY_MARGIN - 2.0f, point.y + KEY_MARGIN); + + if (column_index == KEY_COLUMN_INDEX) + lowColor = ReallyLightPurple; + else + lowColor = LightYellow; + + owner->SetHighColor(lowColor); + owner->FillRoundRect(textRect, CORNER_RADIUS, CORNER_RADIUS); + owner->SetHighColor(Black); + owner->StrokeRoundRect(textRect, CORNER_RADIUS, CORNER_RADIUS); + } + + owner->SetHighColor(Black); + owner->SetLowColor(lowColor); + owner->DrawString(text, point); + // with a cursor at the end if highlighted + if (column_index == STRING_COLUMN_INDEX) { + // Draw cursor + if ((columnSelected) && (selected)) { + point.x += textWidth; + point.y += (fTextOffset / 4.0f); + + BPoint pt2 = point; + pt2.y -= fTextOffset; + owner->StrokeLine(point, pt2); + + fCursorPt1 = point; + fCursorPt2 = pt2; + fCursorPtsValid = true; + } + + BRegion bitmapRegion; + item_column_rect.left -= (STRING_COLUMN_LEFT_MARGIN - 4.0f); + item_column_rect.right = item_column_rect.left + 16.0f; + item_column_rect.top += 3.0f; + item_column_rect.bottom = item_column_rect.top + 16.0f; + + bitmapRegion.Include(item_column_rect); + owner->ConstrainClippingRegion(&bitmapRegion); + owner->SetDrawingMode(B_OP_OVER); + + if ((fCommand != NULL) && (fCommand[0] == '*')) + owner->DrawBitmap(_actuatorBitmaps[fBitmapValid ? 1 : 0], + ICON_BITMAP_RECT, item_column_rect); + else + // Draw icon, if any + if (fBitmapValid) + owner->DrawBitmap(&fBitmap, ICON_BITMAP_RECT, + item_column_rect); + } + + owner->SetDrawingMode(B_OP_COPY); + owner->ConstrainClippingRegion(NULL); +} + + +void +ShortcutsSpec::Update(BView* owner, const BFont* font) +{ + CLVListItem::Update(owner, font); + font_height FontAttributes; + be_plain_font->GetHeight(&FontAttributes); + float fontHeight = ceil(FontAttributes.ascent) + + ceil(FontAttributes.descent); + fTextOffset = ceil(FontAttributes.ascent) + (Height() - fontHeight) / 2.0; +} + + +const char* +ShortcutsSpec::GetCellText(int whichColumn) const +{ + const char* temp = ""; // default + switch(whichColumn) + { + case KEY_COLUMN_INDEX: + { + if ((fKey > 0) && (fKey <= 0xFF)) { + temp = GetKeyName(fKey); + if (temp == NULL) + temp = ""; + } else if (fKey > 0xFF) { + sprintf(fScratch, "#%x", fKey); + return fScratch; + } + } + break; + + case STRING_COLUMN_INDEX: + temp = fCommand; + break; + + default: + if ((whichColumn >= 0) && (whichColumn < NUM_META_COLUMNS)) + temp = _metaMaps[whichColumn].GetNthStateDesc( + fMetaCellStateIndex[whichColumn]); + break; + } + return temp; +} + + +bool +ShortcutsSpec::ProcessColumnMouseClick(int whichColumn) +{ + if ((whichColumn >= 0) && (whichColumn < NUM_META_COLUMNS)) { + // same as hitting space for these columns: cycle entry + const char temp = B_SPACE; + + // 3rd arg isn't correct but it isn't read for this case anyway + return ProcessColumnKeyStroke(whichColumn, &temp, 0); + } + return false; +} + + +bool +ShortcutsSpec::ProcessColumnTextString(int whichColumn, const char* string) +{ + switch(whichColumn) { + case STRING_COLUMN_INDEX: + SetCommand(string); + return true; + break; + + case KEY_COLUMN_INDEX: + { + fKey = FindKeyCode(string); + return true; + break; + } + + default: + return ProcessColumnKeyStroke(whichColumn, string, 0); + } +} + + +bool +ShortcutsSpec::_AttemptTabCompletion() +{ + bool ret = false; + + int32 argc; + char** argv = ParseArgvFromString(fCommand, argc); + if (argc > 0) { + // Try to complete the path partially expressed in the last argument! + char* arg = argv[argc - 1]; + char* fileFragment = strrchr(arg, '/'); + if (fileFragment) { + const char* directoryName = (fileFragment == arg) ? "/" : arg; + *fileFragment = '\0'; + fileFragment++; + int fragLen = strlen(fileFragment); + + BDirectory dir(directoryName); + if (dir.InitCheck() == B_NO_ERROR) { + BEntry nextEnt; + BPath nextPath; + BList matchList; + int maxEntryLen = 0; + + // Read in all the files in the directory whose names start + // with our fragment. + while (dir.GetNextEntry(&nextEnt) == B_NO_ERROR) { + if (nextEnt.GetPath(&nextPath) == B_NO_ERROR) { + char* filePath = strrchr(nextPath.Path(), '/') + 1; + if (strncmp(filePath, fileFragment, fragLen) == 0) { + int len = strlen(filePath); + if (len > maxEntryLen) + maxEntryLen = len; + char* newStr = new char[len + 1]; + strcpy(newStr, filePath); + matchList.AddItem(newStr); + } + } + } + + // Now slowly extend our keyword to its full length, counting + // numbers of matches at each step. If the match list length + // is 1, we can use that whole entry. If it's greater than one + // , we can use just the match length. + int matchLen = matchList.CountItems(); + if (matchLen > 0) { + int i; + BString result(fileFragment); + for (i = fragLen; i < maxEntryLen; i++) { + // See if all the matching entries have the same letter + // in the next position... if so, we can go farther. + char commonLetter = '\0'; + for (int j = 0; j < matchLen; j++) { + char nextLetter = GetLetterAt( + (char*)matchList.ItemAt(j), i); + if (commonLetter == '\0') + commonLetter = nextLetter; + + if ((commonLetter != '\0') + && (commonLetter != nextLetter)) { + commonLetter = '\0';// failed; + beep(); + break; + } + } + if (commonLetter == '\0') + break; + else + result.Append(commonLetter, 1); + } + + // Free all the strings we allocated + for (int k = 0; k < matchLen; k++) + delete [] ((char*)matchList.ItemAt(k)); + + DoStandardEscapes(result); + + BString wholeLine; + for (int l = 0; l < argc - 1; l++) { + wholeLine += argv[l]; + wholeLine += " "; + } + + BString file(directoryName); + DoStandardEscapes(file); + + if (directoryName[strlen(directoryName) - 1] != '/') + file += "/"; + + file += result; + + // Remove any trailing slash... + const char* fileStr = file.String(); + if (fileStr[strlen(fileStr)-1] == '/') + file.RemoveLast("/"); + + // And re-append it iff the file is a dir. + BDirectory testFileAsDir(file.String()); + if ((strcmp(file.String(), "/") != 0) + && (testFileAsDir.InitCheck() == B_NO_ERROR)) + file.Append("/"); + + wholeLine += file; + + SetCommand(wholeLine.String()); + ret = true; + } + } + *(fileFragment - 1) = '/'; + } + } + FreeArgv(argv); + return ret; +} + + +bool +ShortcutsSpec::ProcessColumnKeyStroke(int whichColumn, const char* bytes, + int32 key) +{ + bool ret = false; + switch(whichColumn) { + case KEY_COLUMN_INDEX: + if (key != -1) { + if (fKey != key) { + fKey = key; + ret = true; + } + } + break; + + case STRING_COLUMN_INDEX: + { + switch(bytes[0]) { + case B_BACKSPACE: + case B_DELETE: + if (fCommandNul > 0) { + //trim a char off the string + fCommand[fCommandNul - 1] = '\0'; + fCommandNul--; // note new nul position + ret = true; + _UpdateIconBitmap(); + } + break; + + case B_TAB: + if (_AttemptTabCompletion()) { + _UpdateIconBitmap(); + ret = true; + } else + beep(); + break; + + default: + { + int newCharLen = strlen(bytes); + if ((newCharLen > 0) && (bytes[0] >= ' ')) { + bool reAllocString = false; + // Make sure we have enough room in our command string + // to add these chars... + while (fCommandLen - fCommandNul <= newCharLen) { + reAllocString = true; + // enough for a while... + fCommandLen = (fCommandLen + 10) * 2; + } + + if (reAllocString) { + char* temp = new char[fCommandLen]; + strcpy(temp, fCommand); + delete [] fCommand; + fCommand = temp; + // fCommandNul is still valid since it's an offset + // and the string length is the same for now + } + + // Here we should be guaranteed enough room. + strncat(fCommand, bytes, fCommandLen); + fCommandNul += newCharLen; + ret = true; + _UpdateIconBitmap(); + } + } + } + } + break; + + default: + if ((whichColumn >= 0) && (whichColumn < NUM_META_COLUMNS)) { + MetaKeyStateMap * map = &_metaMaps[whichColumn]; + int curState = fMetaCellStateIndex[whichColumn]; + int origState = curState; + int numStates = map->GetNumStates(); + + switch(bytes[0]) + { + case B_RETURN: + // cycle to the previous state + curState = (curState + numStates - 1) % numStates; + break; + + case B_SPACE: + // cycle to the next state + curState = (curState + 1) % numStates; + break; + + default: + { + // Go to the state starting with the given letter, if + // any + char letter = bytes[0]; + if (islower(letter)) + letter = toupper(letter); // convert to upper case + + if ((letter == B_BACKSPACE) || (letter == B_DELETE)) + letter = '(';//so space bar will blank out an entry + + for (int i = 0; i < numStates; i++) { + const char* desc = map->GetNthStateDesc(i); + + if (desc) { + if (desc[0] == letter) { + curState = i; + break; + } + } else + printf("Error, NULL state description?\n"); + } + } + break; + } + fMetaCellStateIndex[whichColumn] = curState; + + if (curState != origState) + ret = true; + } + break; + } + + return ret; +} + + +int +ShortcutsSpec::MyCompare(const CLVListItem* a_Item1, const CLVListItem* a_Item2 + , int32 KeyColumn) +{ + ShortcutsSpec* left = (ShortcutsSpec*) a_Item1; + ShortcutsSpec* right = (ShortcutsSpec*) a_Item2; + + int ret = strcmp(left->GetCellText(KeyColumn), + right->GetCellText(KeyColumn)); + return (ret > 0) ? 1 : ((ret == 0) ? 0 : -1); +} + + +void +ShortcutsSpec::Pulse(BView* owner) +{ + if ((fCursorPtsValid)&&(owner->Window()->IsActive())) { + rgb_color prevColor = owner->HighColor(); + rgb_color backgroundColor = (GetSelectedColumn() == + STRING_COLUMN_INDEX) ? BeBackgroundGrey : BeListSelectGrey; + rgb_color barColor = ((GetSelectedColumn() == STRING_COLUMN_INDEX) + && ((system_time() % 1000000) > 500000)) ? Black : backgroundColor; + owner->SetHighColor(barColor); + owner->StrokeLine(fCursorPt1, fCursorPt2); + owner->SetHighColor(prevColor); + } +} + + +void +ShortcutsSpec::_UpdateIconBitmap() +{ + BString firstWord = ParseArgvZeroFromString(fCommand); + + // Only need to change if the first word has changed... + if ((fLastBitmapName == NULL) || (firstWord.Length() == 0) || + (firstWord.Compare(fLastBitmapName))) { + if (firstWord.ByteAt(0) == '*') + fBitmapValid = IsValidActuatorName(&firstWord.String()[1]); + else { + fBitmapValid = false; // default till we prove otherwise! + + if (firstWord.Length() > 0) { + delete [] fLastBitmapName; + fLastBitmapName = new char[firstWord.Length() + 1]; + strcpy(fLastBitmapName, firstWord.String()); + + BEntry progEntry(fLastBitmapName, true); + if ((progEntry.InitCheck() == B_NO_ERROR) + && (progEntry.Exists())) { + BNode progNode(&progEntry); + if (progNode.InitCheck() == B_NO_ERROR) { + BNodeInfo progNodeInfo(&progNode); + if ((progNodeInfo.InitCheck() == B_NO_ERROR) + && (progNodeInfo.GetTrackerIcon(&fBitmap, B_MINI_ICON) + == B_NO_ERROR)) + fBitmapValid = fBitmap.IsValid(); + } + } + } + } + } +} diff --git a/src/preferences/shortcuts/ShortcutsSpec.h b/src/preferences/shortcuts/ShortcutsSpec.h new file mode 100644 index 0000000000..3a6587f8e8 --- /dev/null +++ b/src/preferences/shortcuts/ShortcutsSpec.h @@ -0,0 +1,104 @@ +/* + * Copyright 1999-2009 Haiku Inc. All rights reserved. + * Distributed under the terms of the MIT License. + * + * Authors: + * Jeremy Friesner + */ + + +#ifndef ShortcutsSpec_h +#define ShortcutsSpec_h + +#include + + +#include "CLVListItem.h" +#include "KeyInfos.h" + +class CommandActuator; +class MetaKeyStateMap; + +MetaKeyStateMap & GetNthKeyMap(int which); +void InitializeMetaMaps(); + +/* Objects of this class represent one hotkey "entry" in the preferences + * ListView. Each ShortcutsSpec contains the info necessary to generate both + * the proper GUI display, and the proper BitFieldTester and CommandActuator + * object for the ShortcutsCatcher add-on to use. + */ +class ShortcutsSpec : public CLVListItem { +public: + ShortcutsSpec(const char* command); + ShortcutsSpec(const ShortcutsSpec& copyMe); + ShortcutsSpec(BMessage* from); + ~ShortcutsSpec(); + + virtual status_t Archive(BMessage* into, bool deep = true) const; + virtual void Pulse(BView* owner); + static BArchivable* Instantiate(BMessage* from); + void Update(BView* owner, const BFont* font); + const char* GetCellText(int whichColumn) const; + void SetCommand(const char* commandStr); + + virtual void DrawItemColumn(BView* owner, BRect item_column_rect, int32 column_index, bool columnSelected, bool complete); + + static int MyCompare(const CLVListItem* a_Item1, + const CLVListItem* a_Item2, int32 KeyColumn); + + // Returns the name of the Nth Column. + static const char* GetColumnName(int index); + + // Update this spec's state in response to a keystroke to the given + // column. Returns true iff a change occurred. + bool ProcessColumnKeyStroke(int whichColumn, + const char* bytes, int32 key); + + // Same as ProcessColumnKeyStroke, but for a mouse click instead. + bool ProcessColumnMouseClick(int whichColumn); + + // Same as ProcessColumnKeyStroke, but for a text string instead. + bool ProcessColumnTextString(int whichColumn, + const char* string); + + int32 GetSelectedColumn() const {return fSelectedColumn;} + void SetSelectedColumn(int32 i) {fSelectedColumn = i;} + + // default layout of columns is set in here. + enum { + SHIFT_COLUMN_INDEX = 0, + CONTROL_COLUMN_INDEX = 1, + COMMAND_COLUMN_INDEX = 2, + OPTION_COLUMN_INDEX = 3, + NUM_META_COLUMNS = 4, // shift, control, command, option, for now + KEY_COLUMN_INDEX = NUM_META_COLUMNS, + STRING_COLUMN_INDEX = 5 + }; + +private: + void _CacheViewFont(BView* owner); + bool _AttemptTabCompletion(); + + // call this to ensure the icon is up-to-date + void _UpdateIconBitmap(); + + char* fCommand; + uint32 fCommandLen; // number of bytes in fCommand buffer + uint32 fCommandNul; // index of the NUL byte in fCommand + float fTextOffset; + + // icon for associated program. Invalid if none available. + BBitmap fBitmap; + + char* fLastBitmapName; + bool fBitmapValid; + uint32 fKey; + int32 fMetaCellStateIndex[NUM_META_COLUMNS]; + BPoint fCursorPt1; + BPoint fCursorPt2; + bool fCursorPtsValid; + mutable char fScratch[50]; + int32 fSelectedColumn; +}; + +#endif diff --git a/src/preferences/shortcuts/ShortcutsWindow.cpp b/src/preferences/shortcuts/ShortcutsWindow.cpp new file mode 100644 index 0000000000..8c92162a25 --- /dev/null +++ b/src/preferences/shortcuts/ShortcutsWindow.cpp @@ -0,0 +1,722 @@ +/* + * Copyright 1999-2009 Haiku Inc. All rights reserved. + * Distributed under the terms of the MIT License. + * + * Authors: + * Jeremy Friesner + * Fredrik Modéen + */ + + +#include "ShortcutsWindow.h" + + +#include +#include + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ColumnListView.h" + + +#include "KeyInfos.h" +#include "ShortcutsSpec.h" +#include "ParseCommandLine.h" +#include "MetaKeyStateMap.h" +#include "ShortcutsFilterConstants.h" + +// Window sizing constraints +#define MIN_WIDTH 600 +#define MIN_HEIGHT 130 +#define MAX_WIDTH 65535 +#define MAX_HEIGHT 65535 + +// Default window position +#define WINDOW_START_X 30 +#define WINDOW_START_Y 100 + +#define ERROR "Shortcuts Error" +#define WARNING "Shortcuts warning" + +// Global constants for Shortcuts +#define V_SPACING 5 // vertical spacing between GUI components + + +// Creates a pop-up-menu that reflects the possible states of the specified +// meta-key. +static BPopUpMenu* CreateMetaPopUp(int col); +static BPopUpMenu* CreateMetaPopUp(int col) +{ + MetaKeyStateMap& map = GetNthKeyMap(col); + BPopUpMenu * popup = new BPopUpMenu(NULL, false); + int numStates = map.GetNumStates(); + + for (int i = 0; i < numStates; i++) + popup->AddItem(new BMenuItem(map.GetNthStateDesc(i), NULL)); + + return popup; +} + +// Creates a pop-up that allows the user to choose a key-cap visually +static BPopUpMenu* CreateKeysPopUp(); +static BPopUpMenu* CreateKeysPopUp() +{ + BPopUpMenu* popup = new BPopUpMenu(NULL, false); + int numKeys = GetNumKeyIndices(); + for (int i = 0; i < numKeys; i++) { + const char* next = GetKeyName(i); + + if (next) + popup->AddItem(new BMenuItem(next, NULL)); + } + return popup; +} + + +ShortcutsWindow::ShortcutsWindow() + : + BWindow(BRect(WINDOW_START_X, WINDOW_START_Y, WINDOW_START_X + MIN_WIDTH, + WINDOW_START_Y + MIN_HEIGHT * 2), "Shortcuts", B_DOCUMENT_WINDOW, 0L), + fSavePanel(NULL), + fOpenPanel(NULL), + fSelectPanel(NULL), + fKeySetModified(false), + fLastOpenWasAppend(false) +{ + InitializeMetaMaps(); + SetSizeLimits(MIN_WIDTH, MAX_WIDTH, MIN_HEIGHT, MAX_HEIGHT); + BMenuBar* menuBar = new BMenuBar(BRect(0, 0, 0, 0), "Menu Bar"); + + BMenu* fileMenu = new BMenu("File"); + fileMenu->AddItem(new BMenuItem("Open KeySet...", + new BMessage(OPEN_KEYSET), 'O')); + fileMenu->AddItem(new BMenuItem("Append KeySet...", + new BMessage(APPEND_KEYSET), 'A')); + fileMenu->AddItem(new BMenuItem("Revert to Saved", + new BMessage(REVERT_KEYSET), 'A')); + fileMenu->AddItem(new BSeparatorItem); + fileMenu->AddItem(new BMenuItem("Save KeySet As...", + new BMessage(SAVE_KEYSET_AS), 'S')); + fileMenu->AddItem(new BSeparatorItem); + fileMenu->AddItem(new BMenuItem("About Shortcuts", new BMessage(B_ABOUT_REQUESTED))); + fileMenu->AddItem(new BSeparatorItem); + fileMenu->AddItem(new BMenuItem("Quit", new BMessage(B_QUIT_REQUESTED), + 'Q')); + menuBar->AddItem(fileMenu); + + AddChild(menuBar); + + font_height fh; + be_plain_font->GetHeight(&fh); + float vButtonHeight = ceil(fh.ascent) + ceil(fh.descent) + 5.0f; + + BRect tableBounds = Bounds(); + tableBounds.top = menuBar->Bounds().bottom + 1; + tableBounds.right -= B_V_SCROLL_BAR_WIDTH; + tableBounds.bottom -= (B_H_SCROLL_BAR_HEIGHT + V_SPACING + vButtonHeight + + V_SPACING * 2); + + BScrollView* containerView; + fColumnListView = new ColumnListView(tableBounds, &containerView, NULL, + B_FOLLOW_ALL_SIDES, B_WILL_DRAW | B_FRAME_EVENTS | B_NAVIGABLE, + B_SINGLE_SELECTION_LIST, true, true, true, B_NO_BORDER); + + fColumnListView->SetEditMessage(new BMessage(HOTKEY_ITEM_MODIFIED), + BMessenger(this)); + + const float metaWidth = 50.0f; + + for (int i = 0; i < ShortcutsSpec::NUM_META_COLUMNS; i++) + fColumnListView->AddColumn( + new CLVColumn(ShortcutsSpec::GetColumnName(i), CreateMetaPopUp(i), + metaWidth, CLV_SORT_KEYABLE)); + + fColumnListView->AddColumn(new CLVColumn("Key", CreateKeysPopUp(), 60, + CLV_SORT_KEYABLE)); + + BPopUpMenu* popup = new BPopUpMenu(NULL, false); + popup->AddItem(new BMenuItem("(Choose App with File Requester)", NULL)); + popup->AddItem(new BMenuItem("*InsertString \"Your Text Here\"", NULL)); + popup->AddItem(new BMenuItem("*MoveMouse +20 +0", NULL)); + popup->AddItem(new BMenuItem("*MoveMouseTo 50% 50%", NULL)); + popup->AddItem(new BMenuItem("*MouseButton 1", NULL)); + popup->AddItem(new BMenuItem("*LaunchHandler text/html", NULL)); + popup->AddItem(new BMenuItem( + "*Multi \"*MoveMouseTo 100% 0\" \"*MouseButton 1\"", NULL)); + popup->AddItem(new BMenuItem("*MouseDown", NULL)); + popup->AddItem(new BMenuItem("*MouseUp", NULL)); + popup->AddItem(new BMenuItem( + "*SendMessage application/x-vnd.Be-TRAK 'Tfnd'", NULL)); + popup->AddItem(new BMenuItem("*Beep", NULL)); + fColumnListView->AddColumn(new CLVColumn("Application", popup, 323.0, + CLV_SORT_KEYABLE)); + + fColumnListView->SetSortFunction(ShortcutsSpec::MyCompare); + AddChild(containerView); + + fColumnListView->SetSelectionMessage(new BMessage(HOTKEY_ITEM_SELECTED)); + fColumnListView->SetTarget(this); + + BRect buttonBounds = Bounds(); + buttonBounds.left += V_SPACING; + buttonBounds.right = ((buttonBounds.right - buttonBounds.left) / 2.0f) + + buttonBounds.left; + buttonBounds.bottom -= V_SPACING * 2; + buttonBounds.top = buttonBounds.bottom - vButtonHeight; + buttonBounds.right -= B_V_SCROLL_BAR_WIDTH; + float origRight = buttonBounds.right; + buttonBounds.right = (buttonBounds.left + origRight) * 0.40f - + (V_SPACING / 2); + AddChild(fAddButton = new ResizableButton(Bounds(), buttonBounds, "add", + "Add New Shortcut", new BMessage(ADD_HOTKEY_ITEM))); + buttonBounds.left = buttonBounds.right + V_SPACING; + buttonBounds.right = origRight; + AddChild(fRemoveButton = new ResizableButton(Bounds(), buttonBounds, + "remove", "Remove Selected Shortcut", + new BMessage(REMOVE_HOTKEY_ITEM))); + + fRemoveButton->SetEnabled(false); + + float offset = (buttonBounds.right - buttonBounds.left) / 2.0f; + BRect saveButtonBounds = buttonBounds; + saveButtonBounds.right = Bounds().right - B_V_SCROLL_BAR_WIDTH - offset; + saveButtonBounds.left = buttonBounds.right + V_SPACING + offset; + AddChild(fSaveButton = new ResizableButton(Bounds(), saveButtonBounds, + "save", "Save & Apply", new BMessage(SAVE_KEYSET))); + + fSaveButton->SetEnabled(false); + + entry_ref ref; + if (_GetSettingsFile(&ref)) { + BMessage msg(B_REFS_RECEIVED); + msg.AddRef("refs", &ref); + msg.AddString("startupRef", "please"); + PostMessage(&msg); // Tell ourself to load this file if it exists. + } + Show(); +} + + +ShortcutsWindow::~ShortcutsWindow() +{ + delete fSavePanel; + delete fOpenPanel; + delete fSelectPanel; + be_app->PostMessage(B_QUIT_REQUESTED); +} + + +bool +ShortcutsWindow::QuitRequested() +{ + bool ret = true; + + if (fKeySetModified) { + BAlert* alert = new BAlert(WARNING, + "Really quit without saving your changes?", "Don't Save", "Cancel", + "Save"); + switch(alert->Go()) { + case 1: + ret = false; + break; + + case 2: + // Save: automatically if possible, otherwise go back and open + // up the file requester + if (fLastSaved.InitCheck() == B_NO_ERROR) { + if (_SaveKeySet(fLastSaved) == false) { + (new BAlert(ERROR, + "Shortcuts was unable to save your KeySet file!", + "Oh no"))->Go(); + ret = true; //quit anyway + } + } else { + PostMessage(SAVE_KEYSET); + ret = false; + } + break; + default: + ret = true; + break; + } + } + + if (ret) + fColumnListView->DeselectAll(); // avoid mysterious crash on PPC!? + return ret; +} + + +bool +ShortcutsWindow::_GetSettingsFile(entry_ref* eref) +{ + BPath path; + if (find_directory(B_USER_SETTINGS_DIRECTORY, &path) != B_OK) + return false; + else + path.Append(SHORTCUTS_SETTING_FILE_NAME); + + if (BEntry(path.Path(), true).GetRef(eref) == B_NO_ERROR) + return true; + else + return false; +} + + +// Saves a settings file to (saveEntry). Returns true iff successful. +bool +ShortcutsWindow::_SaveKeySet(BEntry& saveEntry) +{ + BFile saveTo(&saveEntry, B_WRITE_ONLY | B_CREATE_FILE | B_ERASE_FILE); + if (saveTo.InitCheck() != B_NO_ERROR) + return false; + + BMessage saveMsg; + for (int i = 0; i < fColumnListView->CountItems(); i++) { + BMessage next; + if (((ShortcutsSpec*)fColumnListView->ItemAt(i))->Archive(&next) + == B_NO_ERROR) + saveMsg.AddMessage("spec", &next); + else + printf("Error archiving ShortcutsSpec #%i!\n",i); + } + + bool ret = (saveMsg.Flatten(&saveTo) == B_NO_ERROR); + + if (ret) { + fKeySetModified = false; + fSaveButton->SetEnabled(false); + } + + return ret; +} + + +// Appends new entries from the file specified in the "spec" entry of +// (loadMsg). Returns true iff successful. +bool +ShortcutsWindow::_LoadKeySet(const BMessage& loadMsg) +{ + int i = 0; + BMessage msg; + while (loadMsg.FindMessage("spec", i++, &msg) == B_NO_ERROR) { + ShortcutsSpec* spec = (ShortcutsSpec*)ShortcutsSpec::Instantiate(&msg); + if (spec != NULL) + fColumnListView->AddItem(spec); + else + printf("_LoadKeySet: Error parsing spec!\n"); + } + return true; +} + + +// Creates a new entry and adds it to the GUI. (defaultCommand) will be the +// text in the entry, or NULL if no text is desired. +void +ShortcutsWindow::_AddNewSpec(const char* defaultCommand) +{ + _MarkKeySetModified(); + + ShortcutsSpec* spec; + int curSel = fColumnListView->CurrentSelection(); + if (curSel >= 0) { + spec = new ShortcutsSpec(*((ShortcutsSpec*) + fColumnListView->ItemAt(curSel))); + + if (defaultCommand) + spec->SetCommand(defaultCommand); + } else + spec = new ShortcutsSpec(defaultCommand ? defaultCommand : ""); + + fColumnListView->AddItem(spec); + fColumnListView->Select(fColumnListView->CountItems() - 1); + fColumnListView->ScrollToSelection(); +} + + +void +ShortcutsWindow::MessageReceived(BMessage* msg) +{ + switch(msg->what) { + case OPEN_KEYSET: + case APPEND_KEYSET: + fLastOpenWasAppend = (msg->what == APPEND_KEYSET); + if (fOpenPanel) + fOpenPanel->Show(); + else { + BMessenger m(this); + fOpenPanel = new BFilePanel(B_OPEN_PANEL, &m, NULL, 0, false); + fOpenPanel->Show(); + } + fOpenPanel->SetButtonLabel(B_DEFAULT_BUTTON, fLastOpenWasAppend ? + "Append" : "Open"); + break; + + case REVERT_KEYSET: + { + // Send a message to myself, to get me to reload the settings file + fLastOpenWasAppend = false; + BMessage reload(B_REFS_RECEIVED); + entry_ref eref; + _GetSettingsFile(&eref); + reload.AddRef("refs", &eref); + reload.AddString("startupRef", "yeah"); + PostMessage(&reload); + } + break; + + // Respond to drag-and-drop messages here + case B_SIMPLE_DATA: + { + int i = 0; + + entry_ref ref; + while (msg->FindRef("refs", i++, &ref) == B_NO_ERROR) { + BEntry entry(&ref); + if (entry.InitCheck() == B_NO_ERROR) { + BPath path(&entry); + + if (path.InitCheck() == B_NO_ERROR) { + // Add a new item with the given path. + BString str(path.Path()); + DoStandardEscapes(str); + _AddNewSpec(str.String()); + } + } + } + } + break; + + // Respond to FileRequester's messages here + case B_REFS_RECEIVED: + { + // Find file ref + entry_ref ref; + bool isStartMsg = msg->HasString("startupRef"); + if (msg->FindRef("refs", &ref) == B_NO_ERROR) { + // load the file into (fileMsg) + BMessage fileMsg; + { + BFile file(&ref, B_READ_ONLY); + if ((file.InitCheck() != B_NO_ERROR) + || (fileMsg.Unflatten(&file) != B_NO_ERROR)) { + if (isStartMsg) { + // use this to save to anyway + fLastSaved = BEntry(&ref); + break; + } else { + (new BAlert(ERROR, + "Shortcuts was couldn't open your KeySet file!" + , "Okay"))->Go(NULL); + break; + } + } + } + + if (fLastOpenWasAppend == false) { + // Clear the menu... + ShortcutsSpec * item; + do { + delete (item = ((ShortcutsSpec*) + fColumnListView->RemoveItem(int32(0)))); + } while (item); + } + + if (_LoadKeySet(fileMsg)) { + if (isStartMsg) fLastSaved = BEntry(&ref); + fSaveButton->SetEnabled(isStartMsg == false); + + // If we just loaded in the Shortcuts settings file, then + // no need to tell the user to save on exit. + entry_ref eref; + _GetSettingsFile(&eref); + if (ref == eref) fKeySetModified = false; + } else { + (new BAlert(ERROR, + "Shortcuts was unable to parse your KeySet file!", + "Okay"))->Go(NULL); + break; + } + } + } + break; + + // These messages come from the pop-up menu of the Applications column + case SELECT_APPLICATION: + { + int csel = fColumnListView->CurrentSelection(); + if (csel >= 0) { + entry_ref aref; + if (msg->FindRef("refs", &aref) == B_NO_ERROR) { + BEntry ent(&aref); + if (ent.InitCheck() == B_NO_ERROR) { + BPath path; + if ((ent.GetPath(&path) == B_NO_ERROR) + && (((ShortcutsSpec *) + fColumnListView->ItemAt(csel))-> + ProcessColumnTextString(ShortcutsSpec:: + STRING_COLUMN_INDEX, path.Path()))) { + + fColumnListView->InvalidateItem(csel); + _MarkKeySetModified(); + } + } + } + } + } + break; + + case SAVE_KEYSET: + { + bool showSaveError = false; + + const char * name; + entry_ref entry; + if ((msg->FindString("name", &name) == B_NO_ERROR) + && (msg->FindRef("directory", &entry) == B_NO_ERROR)) { + BDirectory dir(&entry); + BEntry saveTo(&dir, name, true); + showSaveError = ((saveTo.InitCheck() != B_NO_ERROR) + || (_SaveKeySet(saveTo) == false)); + } else if (fLastSaved.InitCheck() == B_NO_ERROR) { + // We've saved this before, save over previous file. + showSaveError = (_SaveKeySet(fLastSaved) == false); + } else PostMessage(SAVE_KEYSET_AS); // open the save requester... + + if (showSaveError) { + (new BAlert(ERROR, "Shortcuts wasn't able to save your keyset." + , "Okay"))->Go(NULL); + } + } + break; + + case SAVE_KEYSET_AS: + { + if (fSavePanel) + fSavePanel->Show(); + else { + BMessage msg(SAVE_KEYSET); + BMessenger messenger(this); + fSavePanel = new BFilePanel(B_SAVE_PANEL, &messenger, NULL, 0, + false, &msg); + fSavePanel->Show(); + } + } + break; + + case B_ABOUT_REQUESTED: + be_app_messenger.SendMessage(B_ABOUT_REQUESTED); + break; + + case ADD_HOTKEY_ITEM: + _AddNewSpec(NULL); + break; + + case REMOVE_HOTKEY_ITEM: + { + int index = fColumnListView->CurrentSelection(); + if (index >= 0) { + CLVListItem* item = (CLVListItem*) + fColumnListView->ItemAt(index); + fColumnListView->RemoveItem(index); + delete item; + _MarkKeySetModified(); + + // Rules for new selection: If there is an item at (index), + // select it. Otherwise, if there is an item at (index-1), + // select it. Otherwise, select nothing. + int num = fColumnListView->CountItems(); + if (num > 0) { + if (index < num) + fColumnListView->Select(index); + else { + if (index > 0) + index--; + if (index < num) + fColumnListView->Select(index); + } + } + } + } + break; + + // Received when the user clicks on the ColumnListView + case HOTKEY_ITEM_SELECTED: + { + int32 index = -1; + msg->FindInt32("index", &index); + bool validItem = (index >= 0); + fRemoveButton->SetEnabled(validItem); + } + break; + + // Received when an entry is to be modified in response to GUI activity + case HOTKEY_ITEM_MODIFIED: + { + int32 row, column; + + if ((msg->FindInt32("row", &row) == B_NO_ERROR) + && (msg->FindInt32("column", &column) == B_NO_ERROR)) { + int32 key; + const char* bytes; + + if (row >= 0) { + ShortcutsSpec* item = (ShortcutsSpec*) + fColumnListView->ItemAt(row); + bool repaintNeeded = false; // default + + if (msg->HasInt32("mouseClick")) { + repaintNeeded = item->ProcessColumnMouseClick(column); + } else if ((msg->FindString("bytes", &bytes) == B_NO_ERROR) + && (msg->FindInt32("key", &key) == B_NO_ERROR)) { + repaintNeeded = item->ProcessColumnKeyStroke(column, + bytes, key); + } else if (msg->FindInt32("unmappedkey", &key) == + B_NO_ERROR) { + repaintNeeded = ((column == item->KEY_COLUMN_INDEX) + && ((key > 0xFF) || (GetKeyName(key) != NULL)) + && (item->ProcessColumnKeyStroke(column, NULL, + key))); + } else if (msg->FindString("text", &bytes) == B_NO_ERROR) { + if ((bytes[0] == '(')&&(bytes[1] == 'C')) { + if (fSelectPanel) + fSelectPanel->Show(); + else { + BMessage msg(SELECT_APPLICATION); + BMessenger m(this); + fSelectPanel = new BFilePanel(B_OPEN_PANEL, &m, + NULL, 0, false, &msg); + fSelectPanel->Show(); + } + fSelectPanel->SetButtonLabel(B_DEFAULT_BUTTON, + "Select"); + } else + repaintNeeded = item->ProcessColumnTextString( + column, bytes); + } + + if (repaintNeeded) { + fColumnListView->InvalidateItem(row); + _MarkKeySetModified(); + } + } + } + } + break; + + default: + BWindow::MessageReceived(msg); + break; + } +} + + +void +ShortcutsWindow::_MarkKeySetModified() +{ + if (fKeySetModified == false) { + fKeySetModified = true; + fSaveButton->SetEnabled(true); + } +} + + +void +ShortcutsWindow::Quit() +{ + for (int i = fColumnListView->CountItems() - 1; i >= 0; i--) + delete (ShortcutsSpec*)fColumnListView->ItemAt(i); + + fColumnListView->MakeEmpty(); + BWindow::Quit(); +} + + +void +ShortcutsWindow::FrameResized(float w, float h) +{ + fAddButton->ChangeToNewSize(w, h); + fRemoveButton->ChangeToNewSize(w, h); + fSaveButton->ChangeToNewSize(w, h); +} + + +void +ShortcutsWindow::DispatchMessage(BMessage* msg, BHandler* handler) +{ + switch(msg->what) { + case B_COPY: + case B_CUT: + if (be_clipboard->Lock()) { + int32 row = fColumnListView->CurrentSelection(); + int32 column = fColumnListView->GetSelectedColumn(); + if ((row >= 0) + && (column == ShortcutsSpec::STRING_COLUMN_INDEX)) { + ShortcutsSpec* spec = (ShortcutsSpec*) + fColumnListView->ItemAt(row); + if (spec) { + BMessage* data = be_clipboard->Data(); + data->RemoveName("text/plain"); + data->AddData("text/plain", B_MIME_TYPE, + spec->GetCellText(column), + strlen(spec->GetCellText(column))); + be_clipboard->Commit(); + + if (msg->what == B_CUT) { + spec->ProcessColumnTextString(column, ""); + _MarkKeySetModified(); + fColumnListView->InvalidateItem(row); + } + } + } + be_clipboard->Unlock(); + } + break; + + case B_PASTE: + if (be_clipboard->Lock()) { + BMessage* data = be_clipboard->Data(); + const char* text; + ssize_t textLen; + if (data->FindData("text/plain", B_MIME_TYPE, (const void**) + &text, &textLen) == B_NO_ERROR) { + int32 row = fColumnListView->CurrentSelection(); + int32 column = fColumnListView->GetSelectedColumn(); + if ((row >= 0) + && (column == ShortcutsSpec::STRING_COLUMN_INDEX)) { + ShortcutsSpec* spec = (ShortcutsSpec*) + fColumnListView->ItemAt(row); + if (spec) { + for (ssize_t i = 0; i < textLen; i++) { + char buf[2] = {text[i], 0x00}; + spec->ProcessColumnKeyStroke(column, buf, 0); + } + } + fColumnListView->InvalidateItem(row); + _MarkKeySetModified(); + } + } + be_clipboard->Unlock(); + } + break; + + default: + BWindow::DispatchMessage(msg, handler); + break; + } +} diff --git a/src/preferences/shortcuts/ShortcutsWindow.h b/src/preferences/shortcuts/ShortcutsWindow.h new file mode 100644 index 0000000000..b704a5a095 --- /dev/null +++ b/src/preferences/shortcuts/ShortcutsWindow.h @@ -0,0 +1,84 @@ +/* + * Copyright 1999-2009 Haiku Inc. All rights reserved. + * Distributed under the terms of the MIT License. + * + * Authors: + * Jeremy Friesner + */ + + +#ifndef ShortcutsWindow_h +#define ShortcutsWindow_h + +#include +#include +#include +#include +#include + + +#include "ColumnListView.h" +#include "ResizableButton.h" + +// This class defines our preferences/configuration window. +class ShortcutsWindow : public BWindow { +public: +// If (optSaveTo) is non-NULL, settings will be loaded from the file it +// represents. + ShortcutsWindow(); + ~ShortcutsWindow(); + + virtual void DispatchMessage(BMessage* msg, BHandler* handler); + virtual void Quit(); + virtual void FrameResized(float w, float h); + virtual void MessageReceived(BMessage * msg); + virtual bool QuitRequested(); + + // BMessage 'what' codes, representing commands understood by this Window. + enum { + ADD_HOTKEY_ITEM = 'SpKy', // Add a new hotkey entry to the GUI list. + REMOVE_HOTKEY_ITEM, // Remove a hotkey entry from the GUI list. + HOTKEY_ITEM_SELECTED, // Give the "focus bar" to the specified + // entry. + HOTKEY_ITEM_MODIFIED, // Update the state of an entry to reflect + // user's changes. + OPEN_KEYSET, // Bring up a File requester to load new + // settings. + APPEND_KEYSET, // Bring up a File requester to append + // settings. + REVERT_KEYSET, // Dump the current state and re-read + // settings from disk. + SAVE_KEYSET, // Save the current settings to disk + SAVE_KEYSET_AS, // Bring up a File requester to save + // current settings. + SELECT_APPLICATION, // Set the current entry to point to the + // given file. + }; +private: + BMenuItem* _CreateActuatorPresetMenuItem(const char* label) + const; + void _AddNewSpec(const char* defaultCommand); + void _MarkKeySetModified(); + bool _LoadKeySet(const BMessage& loadMsg); + bool _SaveKeySet(BEntry & saveEntry); + bool _GetSettingsFile(entry_ref* ref); + + ResizableButton* fAddButton; + ResizableButton* fRemoveButton; + ResizableButton* fSaveButton; + ColumnListView* fColumnListView; + BFilePanel* fSavePanel; // for saving settings + BFilePanel* fOpenPanel; // for loading settings + BFilePanel* fSelectPanel; // for selecting apps to launch + + // Points to the settings file to save to + BEntry fLastSaved; + + // true iff changes were made since last load or save + bool fKeySetModified; + + // true iff the file-requester's ref should be appended to current + bool fLastOpenWasAppend; +}; + +#endif diff --git a/src/preferences/shortcuts/clv/CLVColumn.cpp b/src/preferences/shortcuts/clv/CLVColumn.cpp new file mode 100644 index 0000000000..c53d633c4b --- /dev/null +++ b/src/preferences/shortcuts/clv/CLVColumn.cpp @@ -0,0 +1,217 @@ +//Column list header source file + + +//****************************************************************************************************** +//**** PROJECT HEADER FILES +//****************************************************************************************************** +#define CLVColumn_CPP +#include +#include "CLVColumn.h" +#include "ColumnListView.h" +#include "CLVColumnLabelView.h" + + +//****************************************************************************************************** +//**** CLVColumn CLASS DEFINITION +//****************************************************************************************************** +CLVColumn::CLVColumn(const char* label,BPopUpMenu * popup,float width,uint32 flags,float min_width) +{ + fPopup = popup; + + if(flags & CLV_EXPANDER) + { + label = NULL; + width = 20.0; + min_width = 20.0; + flags &= CLV_NOT_MOVABLE | CLV_LOCK_AT_BEGINNING | CLV_HIDDEN | CLV_LOCK_WITH_RIGHT; + flags |= CLV_EXPANDER | CLV_NOT_RESIZABLE | CLV_MERGE_WITH_RIGHT; + } + if(min_width < 4.0) + min_width = 4.0; + if(width < min_width) + width = min_width; + if(label) + { + char* Temp = new char[strlen(label)+1]; + strcpy(Temp,label); + label = Temp; + } + if(label) + { + fLabel = new char[strlen(label)+1]; + strcpy((char*)fLabel,label); + } + else + fLabel = NULL; + fWidth = width; + fMinWidth = min_width; + fFlags = flags; + fPushedByExpander = false; + fParent = NULL; + fSortMode = NoSort; +} + + +CLVColumn::~CLVColumn() +{ + if(fLabel) delete[] fLabel; + if(fParent) fParent->RemoveColumn(this); + delete fPopup; +} + + +float CLVColumn::Width() const +{ + return fWidth; +} + + +void CLVColumn::SetWidth(float width) +{ + if(width < fMinWidth) + width = fMinWidth; + if(width != fWidth) + { + float OldWidth = fWidth; + fWidth = width; + if(IsShown() && fParent) + { + BWindow* ParentWindow = fParent->Window(); + if(ParentWindow) + ParentWindow->Lock(); + //Figure out the area after this column to scroll + BRect ColumnViewBounds = fParent->fColumnLabelView->Bounds(); + BRect MainViewBounds = fParent->Bounds(); + BRect SourceArea = ColumnViewBounds; + SourceArea.left = fColumnEnd+1.0; + BRect DestArea = SourceArea; + float Delta = width-OldWidth; + DestArea.left += Delta; + DestArea.right += Delta; + float LimitShift; + if(DestArea.right > ColumnViewBounds.right) + { + LimitShift = DestArea.right-ColumnViewBounds.right; + DestArea.right -= LimitShift; + SourceArea.right -= LimitShift; + } + if(DestArea.left < ColumnViewBounds.left) + { + LimitShift = ColumnViewBounds.left - DestArea.left; + DestArea.left += LimitShift; + SourceArea.left += LimitShift; + } + //Scroll the area that is being shifted + if(ParentWindow) + ParentWindow->UpdateIfNeeded(); + fParent->fColumnLabelView->CopyBits(SourceArea,DestArea); + SourceArea.top = MainViewBounds.top; + SourceArea.bottom = MainViewBounds.bottom; + DestArea.top = MainViewBounds.top; + DestArea.bottom = MainViewBounds.bottom; + fParent->CopyBits(SourceArea,DestArea); + + //Invalidate the region that got revealed + DestArea = ColumnViewBounds; + if(width > OldWidth) + { + DestArea.left = fColumnEnd+1.0; + DestArea.right = fColumnEnd+Delta; + } + else + { + DestArea.left = ColumnViewBounds.right+Delta+1.0; + DestArea.right = ColumnViewBounds.right; + } + fParent->fColumnLabelView->Invalidate(DestArea); + DestArea.top = MainViewBounds.top; + DestArea.bottom = MainViewBounds.bottom; + fParent->Invalidate(DestArea); + + //Invalidate the old or new resize handle as necessary + DestArea = ColumnViewBounds; + if(width > OldWidth) + DestArea.left = fColumnEnd; + else + DestArea.left = fColumnEnd + Delta; + DestArea.right = DestArea.left; + fParent->fColumnLabelView->Invalidate(DestArea); + + //Update the column sizes, positions and group positions + fParent->UpdateColumnSizesDataRectSizeScrollBars(); + fParent->fColumnLabelView->UpdateDragGroups(); + if(ParentWindow) + ParentWindow->Unlock(); + } + if(fParent) + fParent->ColumnWidthChanged(fParent->fColumnList.IndexOf(this),fWidth); + } +} + + +uint32 CLVColumn::Flags() const +{ + return fFlags; +} + + +bool CLVColumn::IsShown() const +{ + if(fFlags & CLV_HIDDEN) + return false; + else + return true; +} + + +void CLVColumn::SetShown(bool Shown) +{ + bool shown = IsShown(); + if(shown != Shown) + { + if(Shown) + fFlags &= 0xFFFFFFFF^CLV_HIDDEN; + else + fFlags |= CLV_HIDDEN; + if(fParent) + { + float UpdateLeft = fColumnBegin; + BWindow* ParentWindow = fParent->Window(); + if(ParentWindow) + ParentWindow->Lock(); + fParent->UpdateColumnSizesDataRectSizeScrollBars(); + fParent->fColumnLabelView->UpdateDragGroups(); + if(Shown) + UpdateLeft = fColumnBegin; + BRect Area = fParent->fColumnLabelView->Bounds(); + Area.left = UpdateLeft; + fParent->fColumnLabelView->Invalidate(Area); + Area = fParent->Bounds(); + Area.left = UpdateLeft; + fParent->Invalidate(Area); + if(fFlags & CLV_EXPANDER) + { + if(!Shown) + fParent->fExpanderColumn = -1; + else + fParent->fExpanderColumn = fParent->IndexOfColumn(this); + } + if(ParentWindow) + ParentWindow->Unlock(); + } + } +} + + +CLVSortMode CLVColumn::SortMode() const +{ + return fSortMode; +} + +void CLVColumn::SetSortMode(CLVSortMode mode) +{ + if(fParent) + fParent->SetSortMode(fParent->IndexOfColumn(this),mode); + else + fSortMode = mode; +} diff --git a/src/preferences/shortcuts/clv/CLVColumn.h b/src/preferences/shortcuts/clv/CLVColumn.h new file mode 100644 index 0000000000..89e809d45c --- /dev/null +++ b/src/preferences/shortcuts/clv/CLVColumn.h @@ -0,0 +1,107 @@ +#ifndef CLVColumn_h +#define CLVColumn_h + +#include +#include + +//****************************************************************************************************** +//**** PROJECT HEADER FILES AND CLASS NAME DECLARATIONS +//****************************************************************************************************** +class ColumnListView; +class CLVColumn; +class CLVListItem; + + +//****************************************************************************************************** +//**** CONSTANTS +//****************************************************************************************************** +//Flags +enum +{ + CLV_SORT_KEYABLE = 0x00000001, //Can be used as the sorting key + CLV_NOT_MOVABLE = 0x00000002, //Column can't be moved by user + CLV_NOT_RESIZABLE = 0x00000004, //Column can't be resized by user + CLV_LOCK_AT_BEGINNING = 0x00000008, //Movable columns may not be placed or moved by the user + //into a position before this one + CLV_LOCK_AT_END = 0x00000010, //Movable columns may not be placed or moved by the user + //into a position after this one + CLV_HIDDEN = 0x00000020, //This column is hidden initially + CLV_MERGE_WITH_RIGHT = 0x00000040, //Merge this column label with the one that follows it. + CLV_LOCK_WITH_RIGHT = 0x00000080, //Lock this column to the one that follows it such that + //if the column to the right is moved by the user, this + //one will move with it and vice versa + CLV_EXPANDER = 0x00000100, //Column contains an expander. You may only use one + //expander in a ColumnListView, and an expander may not be + //added to a non-hierarchal ColumnListView. It may not + //have a label. Its width is automatically set to 20.0. + //The only flags that affect it are CLV_NOT_MOVABLE, + //CLV_LOCK_AT_BEGINNING, CLV_NOT_SHOWN and + //CLV_LOCK_WITH_RIGHT. The others are set for you: + //CLV_NOT_RESIZABLE | CLV_MERGE_WITH_RIGHT + CLV_PUSH_PASS = 0x00000200 //Causes this column, if pushed by an expander to the + //left, to pass that push on and also push the next +}; //column to the right. + +enum CLVSortMode +{ + Ascending, + Descending, + NoSort +}; + + +//****************************************************************************************************** +//**** ColumnListView CLASS DECLARATION +//****************************************************************************************************** +class CLVColumn +{ + public: + //Constructor and destructor + CLVColumn( const char* label, + BPopUpMenu * popup = NULL, + float width = 20.0, + uint32 flags = 0L, + float min_width = 20.0); + virtual ~CLVColumn(); + + //Archival stuff + /* Not implemented yet + CLVColumn(BMessage* archive); + static CLVColumn* Instantiate(BMessage* data); + virtual status_t Archive(BMessage* data, bool deep = true) const; + */ + + //Functions + float Width() const; + virtual void SetWidth(float width); //Can be overridden to detect changes to the column width + //however since you are probably overriding + //ColumnListView and dealing with an array of columns + //anyway, it is probably more useful to override + //ColumnListView::ColumnWidthChanged to detect changes to + //column widths + uint32 Flags() const; + bool IsShown() const; + void SetShown(bool shown); + CLVSortMode SortMode() const; + void SetSortMode(CLVSortMode mode); + const char * GetLabel() const {return fLabel;} + BPopUpMenu * GetPopup() {return fPopup;} + + private: + friend class ColumnListView; + friend class CLVColumnLabelView; + friend class CLVListItem; + + const char *fLabel; + float fWidth; + float fMinWidth; + float fColumnBegin; + float fColumnEnd; + uint32 fFlags; + bool fPushedByExpander; + CLVSortMode fSortMode; + ColumnListView* fParent; + BPopUpMenu * fPopup; // added by jaf +}; + +#endif diff --git a/src/preferences/shortcuts/clv/CLVColumnLabelView.cpp b/src/preferences/shortcuts/clv/CLVColumnLabelView.cpp new file mode 100644 index 0000000000..78d380ccac --- /dev/null +++ b/src/preferences/shortcuts/clv/CLVColumnLabelView.cpp @@ -0,0 +1,756 @@ +//ColumnLabelView class source file + + +//****************************************************************************************************** +//**** PROJECT HEADER FILES +//****************************************************************************************************** +#define CLVColumnLabelView_CPP +#include "CLVColumnLabelView.h" +#include "ColumnListView.h" +#include "CLVColumn.h" +#include "MouseWatcher.h" + + +//****************************************************************************************************** +//**** FUNCTION DEFINITIONS +//****************************************************************************************************** +CLVColumnLabelView::CLVColumnLabelView(BRect Bounds,ColumnListView* parent,const BFont* Font) +: BView(Bounds,NULL,B_FOLLOW_LEFT_RIGHT|B_FOLLOW_TOP,B_WILL_DRAW|B_FRAME_EVENTS), +fDragGroups(10) +{ + SetFont(Font); + SetViewColor(BeBackgroundGrey); + SetLowColor(BeBackgroundGrey); + SetHighColor(Black); + fParent = parent; + fDisplayList = &fParent->fColumnDisplayList; + fColumnClicked = NULL; + fColumnDragging = false; + fColumnResizing = false; + font_height FontAttributes; + Font->GetHeight(&FontAttributes); + fFontAscent = ceil(FontAttributes.ascent); +} + + +CLVColumnLabelView::~CLVColumnLabelView() +{ + int32 NumberOfGroups = fDragGroups.CountItems(); + for(int32 Counter = 0; Counter < NumberOfGroups; Counter++) + fDragGroups.RemoveItem(int32(0)); +} + + +void CLVColumnLabelView::Draw(BRect UpdateRect) +{ + BRegion ClippingRegion; + GetClippingRegion(&ClippingRegion); + BRect ViewBounds = Bounds(); + + //Draw each column label in turn + float ColumnBegin = 0.0; + float ColumnEnd = -1.0; + bool MergeWithLeft = false; + int32 NumberOfColumns = fDisplayList->CountItems(); + BPoint Start,Stop; + for(int32 ColumnDraw = 0; ColumnDraw < NumberOfColumns; ColumnDraw++) + { + CLVColumn* ThisColumn = (CLVColumn*)fDisplayList->ItemAt(ColumnDraw); + if(ThisColumn->IsShown()) + { + //Figure out where this column is + ColumnBegin = ThisColumn->fColumnBegin; + ColumnEnd = ThisColumn->fColumnEnd; + //Start by figuring out if this column will merge with a shown column to the right + bool MergeWithRight = false; + if(ThisColumn->fFlags & CLV_MERGE_WITH_RIGHT) + { + for(int32 ColumnCounter = ColumnDraw+1; ColumnCounter < NumberOfColumns; + ColumnCounter++) + { + CLVColumn* NextColumn = (CLVColumn*)fDisplayList->ItemAt(ColumnCounter); + if(NextColumn->IsShown()) + { + //The next column is shown + MergeWithRight = true; + break; + } + else if(!(NextColumn->fFlags & CLV_MERGE_WITH_RIGHT)) + //The next column is not shown and doesn't pass on the merge + break; + } + } + if(ClippingRegion.Intersects(BRect(ColumnBegin,ViewBounds.top,ColumnEnd, + ViewBounds.bottom))) + { + //Need to draw this column + BeginLineArray(4); + //Top line + Start.Set(ColumnBegin,ViewBounds.top); + Stop.Set(ColumnEnd-1.0,ViewBounds.top); + if(MergeWithRight && !(ThisColumn == fColumnClicked && fColumnResizing)) + Stop.x = ColumnEnd; + AddLine(Start,Stop,BeHighlight); + //Left line + if(!MergeWithLeft) + AddLine(BPoint(ColumnBegin,ViewBounds.top+1.0), + BPoint(ColumnBegin,ViewBounds.bottom),BeHighlight); + //Bottom line + Start.Set(ColumnBegin+1.0,ViewBounds.bottom); + if(MergeWithLeft) + Start.x = ColumnBegin; + Stop.Set(ColumnEnd-1.0,ViewBounds.bottom); + if(MergeWithRight && !(ThisColumn == fColumnClicked && fColumnResizing)) + Stop.x = ColumnEnd; + AddLine(Start,Stop,BeShadow); + //Right line + if(ThisColumn == fColumnClicked && fColumnResizing) + AddLine(BPoint(ColumnEnd,ViewBounds.top),BPoint(ColumnEnd,ViewBounds.bottom), + BeFocusBlue); + else if(!MergeWithRight) + AddLine(BPoint(ColumnEnd,ViewBounds.top),BPoint(ColumnEnd,ViewBounds.bottom), + BeShadow); + EndLineArray(); + + //Add the label + if(ThisColumn->fLabel) + { + //Limit the clipping region to the interior of the box + BRegion TextRegion; + TextRegion.Include(BRect(ColumnBegin+1.0,ViewBounds.top+1.0,ColumnEnd-1.0, + ViewBounds.bottom-1.0)); + ConstrainClippingRegion(&TextRegion); + + //Draw the label + if(ThisColumn == fColumnClicked && !fColumnResizing) + SetHighColor(BeFocusBlue); + SetDrawingMode(B_OP_OVER); + DrawString(ThisColumn->fLabel,BPoint(ColumnBegin+9.0,ViewBounds.top+2.0+fFontAscent)); + SetDrawingMode(B_OP_COPY); + + //Underline if this is a selected sort column + if(fParent->fSortKeyList.HasItem(ThisColumn) && ThisColumn->fSortMode != NoSort) + { + float Width = StringWidth(ThisColumn->fLabel); + StrokeLine(BPoint(ColumnBegin+8.0,ViewBounds.top+2.0+fFontAscent+2.0), + BPoint(ColumnBegin+8.0+Width,ViewBounds.top+2.0+fFontAscent+2.0)); + } + if(ThisColumn == fColumnClicked && !fColumnResizing) + SetHighColor(Black); + + //Restore the clipping region + ConstrainClippingRegion(NULL); + } + } + //Set MergeWithLeft flag for the next column to the appropriate state + MergeWithLeft = MergeWithRight; + } + } + + //Add highlight and shadow to the region after the columns if necessary + if(ColumnEnd < ViewBounds.right) + { + ColumnBegin = ColumnEnd+1.0; + if(ClippingRegion.Intersects(BRect(ColumnEnd+1.0,ViewBounds.top,ViewBounds.right, + ViewBounds.bottom))) + { + BeginLineArray(3); + //Top line + AddLine(BPoint(ColumnBegin,ViewBounds.top),BPoint(ViewBounds.right,ViewBounds.top), + BeHighlight); + //Left line + AddLine(BPoint(ColumnBegin,ViewBounds.top+1.0),BPoint(ColumnBegin,ViewBounds.bottom), + BeHighlight); + //Bottom line + Start.Set(ColumnBegin+1.0,ViewBounds.bottom); + if(MergeWithLeft) + Start.x = ColumnBegin; + Stop.Set(ViewBounds.right,ViewBounds.bottom); + AddLine(Start,Stop,BeShadow); + EndLineArray(); + } + } + + //Draw the dragging box if necessary + if(fColumnClicked && fColumnDragging) + { + float DragOutlineLeft = fPreviousMousePos.x-fDragBoxMouseHoldOffset; + float GroupBegin = ((CLVDragGroup*)fDragGroups.ItemAt(fDragGroup))->GroupBegin; + if(DragOutlineLeft < GroupBegin && fSnapGroupBefore == -1) + DragOutlineLeft = GroupBegin; + if(DragOutlineLeft > GroupBegin && fSnapGroupAfter == -1) + DragOutlineLeft = GroupBegin; + float DragOutlineRight = DragOutlineLeft + fDragBoxWidth; + BeginLineArray(4); + AddLine(BPoint(DragOutlineLeft,ViewBounds.top),BPoint(DragOutlineRight, + ViewBounds.top),BeFocusBlue); + AddLine(BPoint(DragOutlineLeft,ViewBounds.bottom),BPoint(DragOutlineRight, + ViewBounds.bottom),BeFocusBlue); + AddLine(BPoint(DragOutlineLeft,ViewBounds.top+1.0),BPoint(DragOutlineLeft, + ViewBounds.bottom-1.0),BeFocusBlue); + AddLine(BPoint(DragOutlineRight,ViewBounds.top+1.0),BPoint(DragOutlineRight, + ViewBounds.bottom-1.0),BeFocusBlue); + EndLineArray(); + fPrevDragOutlineLeft = DragOutlineLeft; + fPrevDragOutlineRight = DragOutlineRight; + } +} + + +void CLVColumnLabelView::MouseDown(BPoint Point) +{ + //Only pay attention to primary mouse button + bool WatchMouse = false; + BPoint MousePos; + uint32 Buttons; + GetMouse(&MousePos,&Buttons); + if(Buttons == B_PRIMARY_MOUSE_BUTTON) + { + BRect ViewBounds = Bounds(); + + //Make sure no other column was already clicked. If so, just discard the old one and redraw the + //view + if(fColumnClicked != NULL) + { + Invalidate(); + fColumnClicked = NULL; + } + + //Find the column that the user clicked, if any + bool GrabbedResizeTab = false; + int32 NumberOfColumns = fDisplayList->CountItems(); + int32 ColumnFind; + CLVColumn* ThisColumn; + for(ColumnFind = 0; ColumnFind < NumberOfColumns; ColumnFind++) + { + ThisColumn = (CLVColumn*)fDisplayList->ItemAt(ColumnFind); + if(ThisColumn->IsShown()) + { + float ColumnBegin = ThisColumn->fColumnBegin; + float ColumnEnd = ThisColumn->fColumnEnd; + if ((Point.x >= ColumnBegin && Point.x <= ColumnEnd) || + ((ColumnFind == NumberOfColumns-1)&&(Point.x >= ColumnBegin))) // anything after the rightmost column can drag... jaf + { + const float resizeTolerance = 5.0f; // jaf is too clumsy to click on a 2 pixel space. :) + + //User clicked in this column + if(Point.x <= ColumnBegin+resizeTolerance) + { + //User clicked the resize tab preceding this column + for(ColumnFind--; ColumnFind >= 0; ColumnFind--) + { + ThisColumn = (CLVColumn*)fDisplayList->ItemAt(ColumnFind); + if(ThisColumn->IsShown()) + { + GrabbedResizeTab = true; + break; + } + } + } + else if(Point.x >= ColumnEnd-resizeTolerance) + { + //User clicked the resize tab for (after) this column + GrabbedResizeTab = true; + } + else + { + //The user clicked in this column + fColumnClicked = (CLVColumn*)fDisplayList->ItemAt(ColumnFind); + fColumnResizing = false; + fPreviousMousePos = Point; + fMouseClickedPos = Point; + fColumnDragging = false; + SetSnapMinMax(); + fDragBoxMouseHoldOffset = Point.x- + ((CLVDragGroup*)fDragGroups.ItemAt(fDragGroup))->GroupBegin; + Invalidate(BRect(ColumnBegin+1.0,ViewBounds.top+1.0,ColumnEnd-1.0, + ViewBounds.bottom-1.0)); + + //Start watching the mouse + WatchMouse = true; + } + break; + } + } + } + if(GrabbedResizeTab) + { + //The user grabbed a resize tab. See if resizing of this column is allowed + if(!(ThisColumn->fFlags & CLV_NOT_RESIZABLE)) + { + fColumnClicked = (CLVColumn*)fDisplayList->ItemAt(ColumnFind); + fColumnResizing = true; + fPreviousMousePos = Point; + fMouseClickedPos = Point; + fColumnDragging = false; + fResizeMouseHoldOffset = Point.x-fColumnClicked->fColumnEnd; + Invalidate(BRect(fColumnClicked->fColumnEnd,ViewBounds.top,ThisColumn->fColumnEnd, + ViewBounds.bottom)); + + //Start watching the mouse + WatchMouse = true; + } + } + } + if(WatchMouse) + { + thread_id MouseWatcherThread = StartMouseWatcher(this); + if(MouseWatcherThread == B_NO_MORE_THREADS || MouseWatcherThread == B_NO_MEMORY) + fColumnClicked = NULL; + } +} + + +void CLVColumnLabelView::MessageReceived(BMessage *message) +{ + if(message->what != MW_MOUSE_MOVED && message->what != MW_MOUSE_DOWN && message->what != MW_MOUSE_UP) + BView::MessageReceived(message); + else if(fColumnClicked != NULL) + { + BPoint MousePos; + message->FindPoint("where",&MousePos); + uint32 Buttons; + message->FindInt32("buttons",(int32*)&Buttons); + uint32 Modifiers; + message->FindInt32("modifiers",(int32*)&Modifiers); + BRect ViewBounds; + ViewBounds = Bounds(); + uint32 ColumnFlags = fColumnClicked->Flags(); + if(Buttons == B_PRIMARY_MOUSE_BUTTON) + { + //Mouse is still held down + if(!fColumnResizing) + { + //User is clicking or dragging + if((MousePos.xfMouseClickedPos.x+2.0) && + !fColumnDragging) + { + //User is initiating a drag + if(fTheDragGroup->Flags & CLV_NOT_MOVABLE) + { + //Not allowed to drag this column - terminate the click + Invalidate(BRect(fColumnClicked->fColumnBegin,ViewBounds.top, + fColumnClicked->fColumnEnd,ViewBounds.bottom)); + fColumnClicked = NULL; + } + else + { + //Actually initiate a drag + fColumnDragging = true; + fPrevDragOutlineLeft = -1.0; + fPrevDragOutlineRight = -1.0; + } + } + + //Now deal with dragging + if(fColumnDragging) + { + //User is dragging + if(MousePos.xfPreviousMousePos.x) + { + //Mouse moved since I last checked + ViewBounds = Bounds(); + + bool ColumnSnapped; + do + { + //Live dragging of columns + ColumnSnapped = false; + float ColumnsUpdateLeft,ColumnsUpdateRight; + float MainViewUpdateLeft,MainViewUpdateRight; + CLVColumn* LastSwapColumn; + if(fSnapMin != -1.0 && MousePos.x < fSnapMin) + { + //Shift the group left + ColumnsUpdateLeft = fTheShownGroupBefore->GroupBegin; + ColumnsUpdateRight = fTheDragGroup->GroupEnd; + MainViewUpdateLeft = ColumnsUpdateLeft; + MainViewUpdateRight = ColumnsUpdateRight; + LastSwapColumn = fTheShownGroupBefore->LastColumnShown; + if(fTheDragGroup->LastColumnShown->fFlags & CLV_MERGE_WITH_RIGHT) + ColumnsUpdateRight += 1.0; + else if(fTheShownGroupBefore->LastColumnShown->fFlags & CLV_MERGE_WITH_RIGHT) + ColumnsUpdateRight += 1.0; + ShiftDragGroup(fSnapGroupBefore); + ColumnSnapped = true; + } + if(fSnapMax != -1.0 && MousePos.x > fSnapMax) + { + //Shift the group right + ColumnsUpdateLeft = fTheDragGroup->GroupBegin; + ColumnsUpdateRight = fTheShownGroupAfter->GroupEnd; + MainViewUpdateLeft = ColumnsUpdateLeft; + MainViewUpdateRight = ColumnsUpdateRight; + LastSwapColumn = fTheDragGroup->LastColumnShown; + if(fTheDragGroup->LastColumnShown->fFlags & CLV_MERGE_WITH_RIGHT) + ColumnsUpdateRight += 1.0; + else if(fTheShownGroupAfter->LastColumnShown->fFlags & CLV_MERGE_WITH_RIGHT) + ColumnsUpdateRight += 1.0; + ShiftDragGroup(fSnapGroupAfter+1); + ColumnSnapped = true; + } + if(ColumnSnapped) + { + //Redraw the snapped column labels + Invalidate(BRect(ColumnsUpdateLeft,ViewBounds.top,ColumnsUpdateRight, + ViewBounds.bottom)); + BRect MainViewBounds = fParent->Bounds(); + //Modify MainViewUpdateRight if more columns are pushed by expanders + if(LastSwapColumn->fFlags & CLV_EXPANDER || + (LastSwapColumn->fPushedByExpander && (LastSwapColumn->fFlags & + CLV_PUSH_PASS))) + { + int32 NumberOfColumns = fDisplayList->CountItems(); + for(int32 ColumnCounter = fDisplayList->IndexOf(LastSwapColumn)+1; + ColumnCounter < NumberOfColumns; ColumnCounter++) + { + CLVColumn* ThisColumn = + (CLVColumn*)fDisplayList->ItemAt(ColumnCounter); + if(ThisColumn->IsShown()) + { + if(ThisColumn->fPushedByExpander) + MainViewUpdateRight = ThisColumn->fColumnEnd; + else + break; + } + } + } + fParent->Invalidate(BRect(MainViewUpdateLeft,MainViewBounds.top, + MainViewUpdateRight,MainViewBounds.bottom)); + } + }while(ColumnSnapped); + //Erase and redraw the drag rectangle but not the interior to avoid label flicker + float Min = fPrevDragOutlineLeft; + float Max = fPrevDragOutlineRight; + float Min2 = MousePos.x-fDragBoxMouseHoldOffset; + float GroupBegin = ((CLVDragGroup*)fDragGroups.ItemAt(fDragGroup))->GroupBegin; + if(Min2 < GroupBegin && fSnapGroupBefore == -1) + Min2 = GroupBegin; + if(Min2 > GroupBegin && fSnapGroupAfter == -1) + Min2 = GroupBegin; + float Max2 = Min2 + fDragBoxWidth; + float Temp; + if(Min2 < Min || Min == -1.0) + {Temp = Min2;Min2 = Min;Min = Temp;} + if(Max2 > Max || Max == -1.0) + {Temp = Max2;Max2 = Max;Max = Temp;} + Invalidate(BRect(Min,ViewBounds.top+1.0,Min,ViewBounds.bottom-1.0)); + if(Min2 != -1.0) + Invalidate(BRect(Min2,ViewBounds.top+1.0,Min2,ViewBounds.bottom-1.0)); + Invalidate(BRect(Max,ViewBounds.top+1.0,Max,ViewBounds.bottom-1.0)); + if(Max2 != -1.0) + Invalidate(BRect(Max2,ViewBounds.top+1.0,Max2,ViewBounds.bottom-1.0)); + Invalidate(BRect(Min,ViewBounds.top,Max,ViewBounds.top)); + Invalidate(BRect(Min,ViewBounds.bottom,Max,ViewBounds.bottom)); + } + } + } + else + { + //User is resizing the column + if(MousePos.xfPreviousMousePos.x) + { + float NewWidth = MousePos.x - fResizeMouseHoldOffset - fColumnClicked->fColumnBegin; + if(NewWidth < fColumnClicked->fMinWidth) + NewWidth = fColumnClicked->fMinWidth; + if(NewWidth != fColumnClicked->fWidth) + { + fColumnClicked->SetWidth(NewWidth); + fParent->ColumnWidthChanged(fParent->IndexOfColumn(fColumnClicked),NewWidth); + } + } + } + } + else if(Buttons == 0) + { + //Mouse button was released + if(!fColumnDragging && !fColumnResizing) + { + //Column was clicked + if(ColumnFlags&CLV_SORT_KEYABLE) + { + //The column is a "sortable" column + if(!(Modifiers&B_SHIFT_KEY)) + { + //The user wants to select it as the main sorting column + if(fParent->fSortKeyList.ItemAt(0) == fColumnClicked) + //The column was already selected; switch sort modes + fParent->ReverseSortMode(fParent->fColumnList.IndexOf(fColumnClicked)); + else + //The user selected this column for sorting + fParent->SetSortKey(fParent->fColumnList.IndexOf(fColumnClicked)); + } + else + { + //The user wants to add it as a secondary sorting column + if(fParent->fSortKeyList.HasItem(fColumnClicked)) + //The column was already selected; switch sort modes + fParent->ReverseSortMode(fParent->fColumnList.IndexOf(fColumnClicked)); + else + //The user selected this column for sorting + fParent->AddSortKey(fParent->fColumnList.IndexOf(fColumnClicked)); + } + } + } + else if(fColumnDragging) + { + //Column was dragging; erase the drag box but not the interior to avoid label flicker + Invalidate(BRect(fPrevDragOutlineLeft,ViewBounds.top+1.0, + fPrevDragOutlineLeft,ViewBounds.bottom-1.0)); + Invalidate(BRect(fPrevDragOutlineRight,ViewBounds.top+1.0, + fPrevDragOutlineRight,ViewBounds.bottom-1.0)); + Invalidate(BRect(fPrevDragOutlineLeft,ViewBounds.top, + fPrevDragOutlineRight,ViewBounds.top)); + Invalidate(BRect(fPrevDragOutlineLeft,ViewBounds.bottom, + fPrevDragOutlineRight,ViewBounds.bottom)); + } + else + //Column was resizing; erase the drag tab + Invalidate(BRect(fColumnClicked->fColumnEnd,ViewBounds.top,fColumnClicked->fColumnEnd, + ViewBounds.bottom)); + //Unhighlight the label and forget the column + Invalidate(BRect(fColumnClicked->fColumnBegin+1.0,ViewBounds.top+1.0, + fColumnClicked->fColumnEnd-1.0,ViewBounds.bottom-1.0)); + fColumnClicked = NULL; + fColumnDragging = false; + fColumnResizing = false; + } + else + { + //Unused button combination + //Unhighlight the label and forget the column + Invalidate(BRect(fColumnClicked->fColumnBegin+1.0,ViewBounds.top+1.0, + fColumnClicked->fColumnEnd-1.0,ViewBounds.bottom-1.0)); + fColumnClicked = NULL; + fColumnDragging = false; + fColumnResizing = false; + } + fPreviousMousePos = MousePos; + } +} + + +void CLVColumnLabelView::ShiftDragGroup(int32 NewPos) +//Shift the drag group into a new position +{ + int32 NumberOfGroups = fDragGroups.CountItems(); + int32 GroupCounter; + CLVDragGroup* ThisGroup; + int32 NumberOfColumnsInGroup; + int32 ColumnCounter; + BList NewDisplayList; + + //Copy the groups up to the new position + for(GroupCounter = 0; GroupCounter < NewPos; GroupCounter++) + { + if(GroupCounter != fDragGroup) + { + ThisGroup = (CLVDragGroup*)fDragGroups.ItemAt(GroupCounter); + NumberOfColumnsInGroup = ThisGroup->GroupStopDispListIndex - + ThisGroup->GroupStartDispListIndex + 1; + for(ColumnCounter = ThisGroup->GroupStartDispListIndex; ColumnCounter <= + ThisGroup->GroupStopDispListIndex; ColumnCounter++) + NewDisplayList.AddItem(fDisplayList->ItemAt(ColumnCounter)); + } + } + //Copy the group into the new position + ThisGroup = (CLVDragGroup*)fDragGroups.ItemAt(fDragGroup); + NumberOfColumnsInGroup = ThisGroup->GroupStopDispListIndex - ThisGroup->GroupStartDispListIndex + 1; + for(ColumnCounter = ThisGroup->GroupStartDispListIndex; ColumnCounter <= + ThisGroup->GroupStopDispListIndex; ColumnCounter++) + NewDisplayList.AddItem(fDisplayList->ItemAt(ColumnCounter)); + //Copy the rest of the groups, but skip the dragging group + for(GroupCounter = NewPos; GroupCounter < NumberOfGroups; GroupCounter++) + { + if(GroupCounter != fDragGroup) + { + ThisGroup = (CLVDragGroup*)fDragGroups.ItemAt(GroupCounter); + NumberOfColumnsInGroup = ThisGroup->GroupStopDispListIndex - + ThisGroup->GroupStartDispListIndex + 1; + for(ColumnCounter = ThisGroup->GroupStartDispListIndex; ColumnCounter <= + ThisGroup->GroupStopDispListIndex; ColumnCounter++) + NewDisplayList.AddItem(fDisplayList->ItemAt(ColumnCounter)); + } + } + + //Set the new order + *fDisplayList = NewDisplayList; + + //Update columns and drag groups + fParent->UpdateColumnSizesDataRectSizeScrollBars(); + UpdateDragGroups(); + SetSnapMinMax(); + + //Inform the program that the display order changed + int32* NewOrder = fParent->DisplayOrder(); + fParent->DisplayOrderChanged(NewOrder); + delete[] NewOrder; +} + + +void CLVColumnLabelView::UpdateDragGroups() +{ + //Make a copy of the DragGroups list. Use it to store the CLVDragGroup's for recycling + BList TempList(fDragGroups); + fDragGroups.MakeEmpty(); + int32 NumberOfColumns = fDisplayList->CountItems(); + bool ContinueGroup = false; + CLVDragGroup* CurrentGroup; + for(int32 Counter = 0; Counter < NumberOfColumns; Counter++) + { + CLVColumn* CurrentColumn = (CLVColumn*)fDisplayList->ItemAt(Counter); + if(!ContinueGroup) + { + //Recycle or obtain a new CLVDragGroup + CurrentGroup = (CLVDragGroup*)TempList.RemoveItem(int32(0)); + if(CurrentGroup == NULL) + CurrentGroup = new CLVDragGroup; + //Add the CLVDragGroup to the DragGroups list + fDragGroups.AddItem(CurrentGroup); + //Set up the new DragGroup + CurrentGroup->GroupStartDispListIndex = Counter; + CurrentGroup->GroupStopDispListIndex = Counter; + CurrentGroup->Flags = 0; + if(CurrentColumn->IsShown()) + { + CurrentGroup->GroupBegin = CurrentColumn->fColumnBegin; + CurrentGroup->GroupEnd = CurrentColumn->fColumnEnd; + CurrentGroup->LastColumnShown = CurrentColumn; + CurrentGroup->Shown = true; + if(CurrentColumn->fFlags & CLV_LOCK_AT_BEGINNING) + CurrentGroup->AllLockBeginning = true; + else + CurrentGroup->AllLockBeginning = false; + if(CurrentColumn->fFlags & CLV_LOCK_AT_END) + CurrentGroup->AllLockEnd = true; + else + CurrentGroup->AllLockEnd = false; + } + else + { + CurrentGroup->GroupBegin = -1.0; + CurrentGroup->GroupEnd = -1.0; + CurrentGroup->LastColumnShown = NULL; + CurrentGroup->Shown = false; + if(CurrentColumn->fFlags & CLV_LOCK_AT_BEGINNING) + CurrentGroup->AllLockBeginning = true; + else + CurrentGroup->AllLockBeginning = false; + if(CurrentColumn->fFlags & CLV_LOCK_AT_END) + CurrentGroup->AllLockEnd = true; + else + CurrentGroup->AllLockEnd = false; + } + } + else + { + //Add this column to the current DragGroup + CurrentGroup->GroupStopDispListIndex = Counter; + if(CurrentColumn->IsShown()) + { + if(CurrentGroup->GroupBegin == -1.0) + CurrentGroup->GroupBegin = CurrentColumn->fColumnBegin; + CurrentGroup->GroupEnd = CurrentColumn->fColumnEnd; + CurrentGroup->LastColumnShown = CurrentColumn; + CurrentGroup->Shown = true; + } + if(!(CurrentColumn->fFlags & CLV_LOCK_AT_BEGINNING)) + CurrentGroup->AllLockBeginning = false; + if(!(CurrentColumn->fFlags & CLV_LOCK_AT_END)) + CurrentGroup->AllLockEnd = false; + } + CurrentGroup->Flags |= CurrentColumn->fFlags & (CLV_NOT_MOVABLE|CLV_LOCK_AT_BEGINNING| + CLV_LOCK_AT_END); + //See if I should add more columns to this group + if(CurrentColumn->fFlags & CLV_LOCK_WITH_RIGHT) + ContinueGroup = true; + else + ContinueGroup = false; + } + //If any unused groups remain in TempList, delete them + while((CurrentGroup = (CLVDragGroup*)TempList.RemoveItem(int32(0))) != NULL) + delete CurrentGroup; +} + + +void CLVColumnLabelView::SetSnapMinMax() +{ + //Find the column group that the user is dragging and the shown group before it + int32 NumberOfGroups = fDragGroups.CountItems(); + int32 ColumnCount; + fDragGroup = -1; + fTheShownGroupBefore = NULL; + fSnapGroupBefore = -1; + CLVDragGroup* ThisGroup; + int32 GroupCounter; + for(GroupCounter = 0; GroupCounter < NumberOfGroups; GroupCounter++) + { + ThisGroup = (CLVDragGroup*)fDragGroups.ItemAt(GroupCounter); + for(ColumnCount = ThisGroup->GroupStartDispListIndex; ColumnCount <= + ThisGroup->GroupStopDispListIndex; ColumnCount++) + if(fDisplayList->ItemAt(ColumnCount) == fColumnClicked) + { + fDragGroup = GroupCounter; + fTheDragGroup = ThisGroup; + break; + } + if(fDragGroup != -1) + break; + else if(ThisGroup->Shown) + { + fTheShownGroupBefore = ThisGroup; + fSnapGroupBefore = GroupCounter; + } + } + + //Find the position of shown group after the one that the user is dragging + fTheShownGroupAfter = NULL; + fSnapGroupAfter = -1; + for(GroupCounter = fDragGroup+1; GroupCounter < NumberOfGroups; GroupCounter++) + { + ThisGroup = (CLVDragGroup*)fDragGroups.ItemAt(GroupCounter); + if(ThisGroup->Shown) + { + fTheShownGroupAfter = ThisGroup; + fSnapGroupAfter = GroupCounter; + break; + } + } + + //See if it can actually snap in the given direction + if(fSnapGroupBefore != -1) + { + if(fTheShownGroupBefore->Flags & CLV_LOCK_AT_BEGINNING) + if(!fTheDragGroup->AllLockBeginning) + fSnapGroupBefore = -1; + if(fTheDragGroup->Flags & CLV_LOCK_AT_END) + if(!fTheShownGroupBefore->AllLockEnd) + fSnapGroupBefore = -1; + } + if(fSnapGroupAfter != -1) + { + if(fTheShownGroupAfter->Flags & CLV_LOCK_AT_END) + if(!fTheDragGroup->AllLockEnd) + fSnapGroupAfter = -1; + if(fTheDragGroup->Flags & CLV_LOCK_AT_BEGINNING) + if(!fTheShownGroupAfter->AllLockBeginning) + fSnapGroupAfter = -1; + } + + //Find the minumum and maximum positions for the group to snap + fSnapMin = -1.0; + fSnapMax = -1.0; + fDragBoxWidth = fTheDragGroup->GroupEnd-fTheDragGroup->GroupBegin; + if(fSnapGroupBefore != -1) + { + fSnapMin = fTheShownGroupBefore->GroupBegin + fDragBoxWidth; + if(fSnapMin > fTheShownGroupBefore->GroupEnd) + fSnapMin = fTheShownGroupBefore->GroupEnd; + } + if(fSnapGroupAfter != -1) + { + fSnapMax = fTheShownGroupAfter->GroupEnd - fDragBoxWidth; + if(fSnapMax < fTheShownGroupAfter->GroupBegin) + fSnapMax = fTheShownGroupAfter->GroupBegin; + } +} diff --git a/src/preferences/shortcuts/clv/CLVColumnLabelView.h b/src/preferences/shortcuts/clv/CLVColumnLabelView.h new file mode 100644 index 0000000000..159adb1f22 --- /dev/null +++ b/src/preferences/shortcuts/clv/CLVColumnLabelView.h @@ -0,0 +1,76 @@ +#ifndef CLVColumnLabelView_h +#define CLVColumnLabelView_h + +#include +#include + +//****************************************************************************************************** +//**** PROJECT HEADER FILES AND CLASS NAME DECLARATIONS +//****************************************************************************************************** +class ColumnListView; +class CLVColumn; + + +//****************************************************************************************************** +//**** CLASS AND STRUCTURE DECLARATIONS, ASSOCIATED CONSTANTS AND STATIC FUNCTIONS +//****************************************************************************************************** +struct CLVDragGroup +{ + int32 GroupStartDispListIndex; //Indices in the column display list where this group starts + int32 GroupStopDispListIndex; //and finishes + float GroupBegin,GroupEnd; //-1.0 if whole group is hidden + CLVColumn* LastColumnShown; + bool AllLockBeginning; + bool AllLockEnd; + bool Shown; //False if none of the columns in this group are shown + uint32 Flags; //Uses CLV_NOT_MOVABLE, CLV_LOCK_AT_BEGINNING, CLV_LOCK_AT_END +}; + + +class CLVColumnLabelView : public BView +{ + public: + //Constructor and destructor + CLVColumnLabelView(BRect Bounds,ColumnListView* Parent,const BFont* Font); + ~CLVColumnLabelView(); + + //BView overrides + void Draw(BRect UpdateRect); + void MouseDown(BPoint Point); + void MessageReceived(BMessage *message); + + private: + friend class ColumnListView; + friend class CLVColumn; + + float fFontAscent; + BList* fDisplayList; + + //Column select and drag stuff + CLVColumn* fColumnClicked; + BPoint fPreviousMousePos; + BPoint fMouseClickedPos; + bool fColumnDragging; + bool fColumnResizing; + BList fDragGroups; //Groups of CLVColumns that must drag together + int32 fDragGroup; //Index into DragGroups of the group being dragged by user + CLVDragGroup* fTheDragGroup; + CLVDragGroup* fTheShownGroupBefore; + CLVDragGroup* fTheShownGroupAfter; + int32 fSnapGroupBefore, //Index into DragGroups of TheShownGroupBefore and + fSnapGroupAfter; //TheShownGroupAfter, if the group the user is dragging is + //allowed to snap there, otherwise -1 + float fDragBoxMouseHoldOffset,fResizeMouseHoldOffset; + float fDragBoxWidth; //Can include multiple columns; depends on CLV_LOCK_WITH_RIGHT + float fPrevDragOutlineLeft,fPrevDragOutlineRight; + float fSnapMin,fSnapMax; //-1.0 indicates the column can't snap in the given direction + ColumnListView* fParent; + + //Private functions + void ShiftDragGroup(int32 NewPos); + void UpdateDragGroups(); + void SetSnapMinMax(); +}; + +#endif + diff --git a/src/preferences/shortcuts/clv/CLVListItem.cpp b/src/preferences/shortcuts/clv/CLVListItem.cpp new file mode 100644 index 0000000000..2060f765d7 --- /dev/null +++ b/src/preferences/shortcuts/clv/CLVListItem.cpp @@ -0,0 +1,174 @@ +//CLVListItem source file + + +//****************************************************************************************************** +//**** PROJECT HEADER FILES +//****************************************************************************************************** +#define CLVListItem_CPP +#include +#include "CLVListItem.h" +#include "ColumnListView.h" +#include "CLVColumn.h" +#include "PrefilledBitmap.h" + +#include "InterfaceKit.h" + +//****************************************************************************************************** +//**** CLVItem CLASS DEFINITION +//****************************************************************************************************** +CLVListItem::CLVListItem(uint32 level, bool superitem, bool expanded, float minheight) +: BListItem(level, expanded), +fExpanderButtonRect(-1.0,-1.0,-1.0,-1.0), +fExpanderColumnRect(-1.0,-1.0,-1.0,-1.0), +_selectedColumn(-1) +{ + fSuperItem = superitem; + fOutlineLevel = level; + fMinHeight = minheight; +} + + +CLVListItem::~CLVListItem() +{ } + + +bool CLVListItem::IsSuperItem() const +{ + return fSuperItem; +} + + +void CLVListItem::SetSuperItem(bool superitem) +{ + fSuperItem = superitem; +} + + +uint32 CLVListItem::OutlineLevel() const +{ + return fOutlineLevel; +} + +void CLVListItem::SetOutlineLevel(uint32 level) +{ + fOutlineLevel = level; +} + +void CLVListItem::Pulse(BView * owner) +{ + // empty +} + +void CLVListItem::DrawItem(BView* owner, BRect itemRect, bool complete) +{ + BList* DisplayList = &((ColumnListView*)owner)->fColumnDisplayList; + int32 NumberOfColumns = DisplayList->CountItems(); + float PushMax = itemRect.right; + CLVColumn* ThisColumn; + BRect ThisColumnRect = itemRect; + float ExpanderDelta = OutlineLevel() * 20.0; + //Figure out what the limit is for expanders pushing other columns + for(int32 Counter = 0; Counter < NumberOfColumns; Counter++) + { + ThisColumn = (CLVColumn*)DisplayList->ItemAt(Counter); + if((ThisColumn->fFlags & CLV_EXPANDER) || ThisColumn->fPushedByExpander) + PushMax = ThisColumn->fColumnEnd; + } + BRegion ClippingRegion; + if(!complete) + owner->GetClippingRegion(&ClippingRegion); + else + ClippingRegion.Set(itemRect); + float LastColumnEnd = -1.0; + + //Draw the columns + for(int32 Counter = 0; Counter < NumberOfColumns; Counter++) + { + ThisColumn = (CLVColumn*)DisplayList->ItemAt(Counter); + if(!ThisColumn->IsShown()) + continue; + ThisColumnRect.left = ThisColumn->fColumnBegin; + ThisColumnRect.right = LastColumnEnd = ThisColumn->fColumnEnd; + float Shift = 0.0; + if((ThisColumn->fFlags & CLV_EXPANDER) || ThisColumn->fPushedByExpander) + Shift = ExpanderDelta; + if(ThisColumn->fFlags & CLV_EXPANDER) + { + ThisColumnRect.right += Shift; + if(ThisColumnRect.right > PushMax) + ThisColumnRect.right = PushMax; + fExpanderColumnRect = ThisColumnRect; + if(ClippingRegion.Intersects(ThisColumnRect)) + { + //Give the programmer a chance to do his kind of highlighting if the item is selected + int32 actualIndex = ((ColumnListView*)owner)->fColumnList.IndexOf(ThisColumn); + DrawItemColumn(owner, ThisColumnRect, actualIndex, (_selectedColumn == actualIndex), complete); + if(fSuperItem) + { + //Draw the expander, clip manually + float TopOffset = ceil((ThisColumnRect.bottom-ThisColumnRect.top-10.0)/2.0); + float LeftOffset = ThisColumn->fColumnEnd + Shift - 3.0 - 10.0; + float RightClip = LeftOffset + 10.0 - ThisColumnRect.right; + if(RightClip < 0.0) + RightClip = 0.0; + BBitmap* Arrow; + if(IsExpanded()) + Arrow = &((ColumnListView*)owner)->fDownArrow; + else + Arrow = &((ColumnListView*)owner)->fRightArrow; + if(LeftOffset <= ThisColumnRect.right) + { + fExpanderButtonRect.Set(LeftOffset,ThisColumnRect.top+TopOffset, + LeftOffset+10.0-RightClip,ThisColumnRect.top+TopOffset+10.0); + owner->SetDrawingMode(B_OP_OVER); + owner->DrawBitmap(Arrow, BRect(0.0,0.0,10.0-RightClip,10.0),fExpanderButtonRect); + owner->SetDrawingMode(B_OP_COPY); + } + else + fExpanderButtonRect.Set(-1.0,-1.0,-1.0,-1.0); + } + } + } + else + { + ThisColumnRect.left += Shift; + ThisColumnRect.right += Shift; + if(Shift > 0.0 && ThisColumnRect.right > PushMax) + ThisColumnRect.right = PushMax; + if(ThisColumnRect.right >= ThisColumnRect.left && ClippingRegion.Intersects(ThisColumnRect)) + { + int32 actualIndex = ((ColumnListView*)owner)->fColumnList.IndexOf(ThisColumn); + DrawItemColumn(owner, ThisColumnRect, actualIndex, (_selectedColumn == actualIndex), complete); + } + } + } + //Fill the area after all the columns (so the select highlight goes all the way across) + ThisColumnRect.left = LastColumnEnd + 1.0; + ThisColumnRect.right = owner->Bounds().right; + if(ThisColumnRect.left <= ThisColumnRect.right && ClippingRegion.Intersects(ThisColumnRect)) + { + DrawItemColumn(owner, ThisColumnRect,-1, false, complete); + } +} + + +float CLVListItem::ExpanderShift(int32 column_index, BView* owner) +{ + BList* DisplayList = &((ColumnListView*)owner)->fColumnDisplayList; + CLVColumn* ThisColumn = (CLVColumn*)DisplayList->ItemAt(column_index); + float ExpanderDelta = OutlineLevel() * 20.0; + if(!ThisColumn->fPushedByExpander) + ExpanderDelta = 0.0; + return ExpanderDelta; +} + + +void CLVListItem::Update(BView* owner, const BFont* font) +{ + BListItem::Update(owner,font); + float ItemHeight = Height(); + if(ItemHeight < fMinHeight) + ItemHeight = fMinHeight; + SetWidth(((ColumnListView*)owner)->fPageWidth); + SetHeight(ItemHeight); +} diff --git a/src/preferences/shortcuts/clv/CLVListItem.h b/src/preferences/shortcuts/clv/CLVListItem.h new file mode 100644 index 0000000000..4ce55e68b9 --- /dev/null +++ b/src/preferences/shortcuts/clv/CLVListItem.h @@ -0,0 +1,63 @@ +#ifndef CLVListItem_h +#define CLVListItem_h + +#include + +//****************************************************************************************************** +//**** PROJECT HEADER FILES AND CLASS NAME DECLARATIONS +//****************************************************************************************************** +class ColumnListView; + + +//****************************************************************************************************** +//**** CLVItem CLASS DECLARATION +//****************************************************************************************************** +class CLVListItem : public BListItem +{ + public: + //Constructor and destructor + CLVListItem(uint32 level = 0, bool superitem = false, bool expanded = false, float minheight = 0.0); + virtual ~CLVListItem(); + + //Archival stuff + /* Not implemented yet + CLVItem(BMessage* archive); + static CLVItem* Instantiate(BMessage* data); + virtual status_t Archive(BMessage* data, bool deep = true) const; + */ + + virtual void DrawItemColumn(BView* owner, BRect item_column_rect, int32 column_index, bool columnSelected, + bool complete) = 0; //column_index (0-N) is based on the order in which the columns were added + //to the ColumnListView, not the display order. An index of -1 indicates + //that the program needs to draw a blank area beyond the last column. The + //main purpose is to allow the highlighting bar to continue all the way to + //the end of the ColumnListView, even after the end of the last column. + + virtual void DrawItem(BView* owner, BRect itemRect, bool complete); + //In general, you don't need or want to override DrawItem(). + float ExpanderShift(int32 column_index, BView* owner); + virtual void Update(BView* owner, const BFont* font); + bool IsSuperItem() const; + void SetSuperItem(bool superitem); + uint32 OutlineLevel() const; + void SetOutlineLevel(uint32 level); + + virtual void Pulse(BView * owner); // Called periodically when this item is selected. + + int32 GetSelectedColumn() const {return _selectedColumn;} + void SetSelectedColumn(int32 i) {_selectedColumn = i;} + + private: + friend class ColumnListView; + + bool fSuperItem; + uint32 fOutlineLevel; + float fMinHeight; + BRect fExpanderButtonRect; + BRect fExpanderColumnRect; + BList* fSortingContextBList; + ColumnListView* fSortingContextCLV; + int32 _selectedColumn; +}; + +#endif diff --git a/src/preferences/shortcuts/clv/Colors.h b/src/preferences/shortcuts/clv/Colors.h new file mode 100644 index 0000000000..62979032ca --- /dev/null +++ b/src/preferences/shortcuts/clv/Colors.h @@ -0,0 +1,45 @@ +#ifndef JColors_h +#define JColors_h + +//Useful until be gets around to making these sorts of things +//globals akin to be_plain_font, etc. +#include + +//Be standard UI colors +const rgb_color BeBackgroundGrey = {216,216,216, 255}; +const rgb_color BeInactiveControlGrey = {240,240,240, 255}; +const rgb_color BeFocusBlue = {0, 0, 229, 255}; +const rgb_color BeHighlight = {255,255,255, 255}; +const rgb_color BeShadow = {152,152,152, 255}; +const rgb_color BeDarkShadow = {108,108,108, 255}; +const rgb_color BeLightShadow = {194,194,194, 255}; +const rgb_color BeButtonGrey = {232,232,232, 255}; +const rgb_color BeInactiveGrey = {127,127,127, 255}; +const rgb_color BeListSelectGrey = {178,178,178, 255}; +const rgb_color BeTitleBarYellow = {255,203,0, 255}; + +//Other colors +const rgb_color Black = {0, 0, 0, 255}; +const rgb_color White = {255,255,255, 255}; +const rgb_color Red = {255,0, 0, 255}; +const rgb_color Green = {0, 167,0, 255}; +const rgb_color LightGreen = {90, 240,90, 255}; +const rgb_color Blue = {49, 61, 225, 255}; +const rgb_color LightBlue = {64, 162,255, 255}; +const rgb_color Purple = {144,64, 221, 255}; +const rgb_color LightPurple = {166,74, 255, 255}; +const rgb_color Lavender = {193,122,255, 255}; +const rgb_color Yellow = {255,203,0, 255}; +const rgb_color Orange = {255,163,0, 255}; +const rgb_color Flesh = {255,231,186, 255}; +const rgb_color Tan = {208,182,121, 255}; +const rgb_color Brown = {154,110,45, 255}; +const rgb_color LightMetallicBlue = {143,166,240, 255}; +const rgb_color MedMetallicBlue = {75, 96, 154, 255}; +const rgb_color DarkMetallicBlue = {78, 89, 126, 255}; + +const rgb_color ReallyLightPurple = {255,210,255, 255}; +const rgb_color LightYellow = {255,255,210, 255}; + +#endif + diff --git a/src/preferences/shortcuts/clv/ColumnListView.cpp b/src/preferences/shortcuts/clv/ColumnListView.cpp new file mode 100644 index 0000000000..e8fd1fee4c --- /dev/null +++ b/src/preferences/shortcuts/clv/ColumnListView.cpp @@ -0,0 +1,1759 @@ +//Column list view source file + + +//****************************************************************************************************** +//**** PROJECT HEADER FILES +//****************************************************************************************************** +#define ColumnListView_CPP +#include "ColumnListView.h" +#include "CLVColumnLabelView.h" +#include "CLVColumn.h" +#include "CLVListItem.h" + +#include +#include + +//****************************************************************************************************** +//**** BITMAPS +//****************************************************************************************************** +uint8 CLVRightArrowData[132] = +{ + 0xFF, 0xFF, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0x00, 0x12, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0x00, 0x12, 0x12, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0x00, 0x12, 0x12, 0x12, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0x00, 0x12, 0x12, 0x12, 0x12, 0x00, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0x00, 0x12, 0x12, 0x12, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0x00, 0x12, 0x12, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0x00, 0x12, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF +}; +uint8 CLVDownArrowData[132] = +{ + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0xFF, 0x00, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x00, 0xFF, 0xFF, + 0xFF, 0xFF, 0x00, 0x12, 0x12, 0x12, 0x12, 0x12, 0x00, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0x00, 0x12, 0x12, 0x12, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x12, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF +}; + + +//****************************************************************************************************** +//**** ColumnListView CLASS DEFINITION +//****************************************************************************************************** +class CLVContainerView : public BScrollView +{ + public: + CLVContainerView(char* name, BView* target, uint32 resizingMode, uint32 flags, bool horizontal, + bool vertical, border_style border); + ~CLVContainerView(); + bool IsBeingDestroyed; +}; + + +CLVContainerView::CLVContainerView(char* name, BView* target, uint32 resizingMode, uint32 flags, + bool horizontal, bool vertical, border_style border) : +BScrollView(name,target,resizingMode,flags,horizontal,vertical,border) +{ + IsBeingDestroyed = false; +}; + + +CLVContainerView::~CLVContainerView() +{ + IsBeingDestroyed = true; +} + + +ColumnListView::ColumnListView(BRect Frame, BScrollView **ContainerView, const char *Name, + uint32 ResizingMode, uint32 flags, list_view_type Type, bool hierarchical, bool horizontal, + bool vertical, border_style border, const BFont *LabelFont) +: BListView(Frame,Name,Type,B_FOLLOW_ALL_SIDES,flags|B_PULSE_NEEDED), +fColumnList(6), +fColumnDisplayList(6), +fSortKeyList(6), +fFullItemList(32), +fRightArrow(BRect(0.0,0.0,10.0,10.0),B_COLOR_8_BIT,CLVRightArrowData,false,false), +fDownArrow(BRect(0.0,0.0,10.0,10.0),B_COLOR_8_BIT,CLVDownArrowData,false,false), +_selectedColumn(-1), _editMessage(NULL) +{ + fHierarchical = hierarchical; + + //Create the column titles bar view + font_height FontAttributes; + LabelFont->GetHeight(&FontAttributes); + float fLabelFontHeight = ceil(FontAttributes.ascent) + ceil(FontAttributes.descent); + float ColumnLabelViewBottom = Frame.top+1.0+fLabelFontHeight+3.0; + fColumnLabelView = new CLVColumnLabelView(BRect(Frame.left,Frame.top,Frame.right, + ColumnLabelViewBottom),this,LabelFont); + + //Create the container view + CreateContainer(horizontal,vertical,border,ResizingMode,flags); + *ContainerView = fScrollView; + + //Complete the setup + UpdateColumnSizesDataRectSizeScrollBars(); + fColumnLabelView->UpdateDragGroups(); + fExpanderColumn = -1; + fCompare = NULL; +} + + +ColumnListView::~ColumnListView() +{ + //Delete all list columns + int32 ColumnCount = fColumnList.CountItems(); + for(int32 Counter = ColumnCount-1; Counter >= 0; Counter--) + { + CLVColumn* Item = (CLVColumn*)fColumnList.RemoveItem(Counter); + if(Item) + delete Item; + } + //Remove and delete the container view if necessary + if(!fScrollView->IsBeingDestroyed) + { + fScrollView->RemoveChild(this); + delete fScrollView; + } + + delete _editMessage; +} + + +void ColumnListView::CreateContainer(bool horizontal, bool vertical, border_style border, + uint32 ResizingMode, uint32 flags) +{ + BRect ViewFrame = Frame(); + BRect LabelsFrame = fColumnLabelView->Frame(); + + fScrollView = new CLVContainerView(NULL,this,ResizingMode,flags,horizontal,vertical,border); + BRect NewFrame = Frame(); + //Resize the main view to make room for the CLVColumnLabelView + ResizeTo(ViewFrame.right-ViewFrame.left,ViewFrame.bottom-LabelsFrame.bottom-1.0); + MoveTo(NewFrame.left,NewFrame.top+(LabelsFrame.bottom-LabelsFrame.top+1.0)); + fColumnLabelView->MoveTo(NewFrame.left,NewFrame.top); + + //Add the ColumnLabelView + fScrollView->AddChild(fColumnLabelView); + + //Remove and re-add the BListView so that it will draw after the CLVColumnLabelView + fScrollView->RemoveChild(this); + fScrollView->AddChild(this); + + fFillerView = NULL; +} + + +void ColumnListView::AddScrollViewCorner() +{ + BPoint FarCorner = fScrollView->Bounds().RightBottom(); + fFillerView = new ScrollViewCorner(FarCorner.x-B_V_SCROLL_BAR_WIDTH,FarCorner.y-B_H_SCROLL_BAR_HEIGHT); + fScrollView->AddChild(fFillerView); +} + + +void ColumnListView::UpdateColumnSizesDataRectSizeScrollBars() +{ + //Figure out the width + float ColumnBegin; + float ColumnEnd = -1.0; + fDataWidth = 0.0; + bool NextPushedByExpander = false; + int32 NumberOfColumns = fColumnDisplayList.CountItems(); + for(int32 Counter = 0; Counter < NumberOfColumns; Counter++) + { + CLVColumn* Column = (CLVColumn*)fColumnDisplayList.ItemAt(Counter); + if(NextPushedByExpander) + Column->fPushedByExpander = true; + else + Column->fPushedByExpander = false; + if(Column->IsShown()) + { + float ColumnWidth = Column->Width(); + ColumnBegin = ColumnEnd + 1.0; + ColumnEnd = ColumnBegin + ColumnWidth; + Column->fColumnBegin = ColumnBegin; + Column->fColumnEnd = ColumnEnd; + fDataWidth = Column->fColumnEnd; + if(NextPushedByExpander) + if(!(Column->fFlags & CLV_PUSH_PASS)) + NextPushedByExpander = false; + if(Column->fFlags & CLV_EXPANDER) + //Set the next column to be pushed + NextPushedByExpander = true; + } + } + + //Figure out the height + fDataHeight = 0.0; + int32 NumberOfItems = CountItems(); + for(int32 Counter2 = 0; Counter2 < NumberOfItems; Counter2++) + fDataHeight += ItemAt(Counter2)->Height()+1.0; + if(NumberOfItems > 0) + fDataHeight -= 1.0; + + //Update the scroll bars + UpdateScrollBars(); +} + + +void ColumnListView::UpdateScrollBars() +{ + if(fScrollView) + { + //Figure out the bounds and scroll if necessary + BRect ViewBounds; + float DeltaX,DeltaY; + do + { + ViewBounds = Bounds(); + //Figure out the width of the page rectangle + fPageWidth = fDataWidth; + fPageHeight = fDataHeight; + //If view runs past the end, make more visible at the beginning + DeltaX = 0.0; + if(ViewBounds.right > fDataWidth && ViewBounds.left > 0) + { + DeltaX = ViewBounds.right-fDataWidth; + if(DeltaX > ViewBounds.left) + DeltaX = ViewBounds.left; + } + DeltaY = 0.0; + if(ViewBounds.bottom > fDataHeight && ViewBounds.top > 0) + { + DeltaY = ViewBounds.bottom-fDataHeight; + if(DeltaY > ViewBounds.top) + DeltaY = ViewBounds.top; + } + if(DeltaX != 0.0 || DeltaY != 0.0) + { + ScrollTo(BPoint(ViewBounds.left-DeltaX,ViewBounds.top-DeltaY)); + ViewBounds = Bounds(); + } + if(ViewBounds.right-ViewBounds.left > fDataWidth) + fPageWidth = ViewBounds.right; + if(ViewBounds.bottom-ViewBounds.top > fDataHeight) + fPageHeight = ViewBounds.bottom; + }while(DeltaX != 0.0 || DeltaY != 0.0); + + //Figure out the ratio of the bounds rectangle width or height to the page rectangle width or height + float WidthProp = (ViewBounds.right-ViewBounds.left)/fPageWidth; + float HeightProp = (ViewBounds.bottom-ViewBounds.top)/fPageHeight; + + BScrollBar* HScrollBar = fScrollView->ScrollBar(B_HORIZONTAL); + BScrollBar* VScrollBar = fScrollView->ScrollBar(B_VERTICAL); + //Set the scroll bar ranges and proportions. If the whole document is visible, inactivate the + //slider + if(HScrollBar) + { + if(WidthProp >= 1.0 && ViewBounds.left == 0.0) + HScrollBar->SetRange(0.0,0.0); + else + HScrollBar->SetRange(0.0,fPageWidth-(ViewBounds.right-ViewBounds.left)); + HScrollBar->SetProportion(WidthProp); + //Set the step values + HScrollBar->SetSteps(20.0,ViewBounds.right-ViewBounds.left); + } + if(VScrollBar) + { + if(HeightProp >= 1.0 && ViewBounds.top == 0.0) + { + VScrollBar->SetRange(0.0,0.0); + if(fFillerView) + fFillerView->SetViewColor(BeInactiveControlGrey); + } + else + { + VScrollBar->SetRange(0.0,fPageHeight-(ViewBounds.bottom-ViewBounds.top)); + if(fFillerView) + fFillerView->SetViewColor(BeBackgroundGrey); + } + VScrollBar->SetProportion(HeightProp); + } + } +} + + +void ColumnListView::ColumnsChanged() +{ + //Any previous column dragging/resizing will get corrupted, so deselect + if(fColumnLabelView->fColumnClicked) + fColumnLabelView->fColumnClicked = NULL; + + //Update the internal sizes and grouping of the columns and sizes of drag groups + UpdateColumnSizesDataRectSizeScrollBars(); + fColumnLabelView->UpdateDragGroups(); + fColumnLabelView->Invalidate(); + Invalidate(); +} + + +bool ColumnListView::AddColumn(CLVColumn* Column) +//Adds a column to the ColumnListView at the end of the list. Returns true if successful. +{ + int32 NumberOfColumns = fColumnList.CountItems(); + int32 DisplayIndex = NumberOfColumns; + + //Make sure a second Expander is not being added + if(Column->fFlags & CLV_EXPANDER) + { + if(!fHierarchical) + return false; + for(int32 Counter = 0; Counter < NumberOfColumns; Counter++) + if(((CLVColumn*)fColumnList.ItemAt(Counter))->fFlags & CLV_EXPANDER) + return false; + if(Column->IsShown()) + fExpanderColumn = NumberOfColumns; + } + + //Make sure this column hasn't already been added to another ColumnListView + if(Column->fParent != NULL) + return false; + + BWindow* ParentWindow = Window(); + if(ParentWindow) + ParentWindow->Lock(); + //Check if this should be locked at the beginning or end, and adjust its position if necessary + if(!Column->Flags() & CLV_LOCK_AT_END) + { + bool Repeat; + if(Column->Flags() & CLV_LOCK_AT_BEGINNING) + { + //Move it to the beginning, after the last CLV_LOCK_AT_BEGINNING item + DisplayIndex = 0; + Repeat = true; + while(Repeat && DisplayIndex < NumberOfColumns) + { + Repeat = false; + CLVColumn* LastColumn = (CLVColumn*)fColumnDisplayList.ItemAt(DisplayIndex); + if(LastColumn->Flags() & CLV_LOCK_AT_BEGINNING) + { + DisplayIndex++; + Repeat = true; + } + } + } + else + { + //Make sure it isn't after a CLV_LOCK_AT_END item + Repeat = true; + while(Repeat && DisplayIndex > 0) + { + Repeat = false; + CLVColumn* LastColumn = (CLVColumn*)fColumnDisplayList.ItemAt(DisplayIndex-1); + if(LastColumn->Flags() & CLV_LOCK_AT_END) + { + DisplayIndex--; + Repeat = true; + } + } + } + } + + //Add the column to the display list in the appropriate position + fColumnDisplayList.AddItem(Column, DisplayIndex); + + //Add the column to the end of the column list + fColumnList.AddItem(Column); + + //Tell the column it belongs to me now + Column->fParent = this; + + //Set the scroll bars and tell views to update + ColumnsChanged(); + if(ParentWindow) + ParentWindow->Unlock(); + return true; +} + + +bool ColumnListView::AddColumnList(BList* NewColumns) +//Adds a BList of CLVColumn's to the ColumnListView at the position specified, or at the end of the list +//if AtIndex == -1. Returns true if successful. +{ + int32 NumberOfColumns = int32(fColumnList.CountItems()); + int32 NumberOfColumnsToAdd = int32(NewColumns->CountItems()); + + //Make sure a second CLVExpander is not being added + int32 Counter; + int32 NumberOfExpanders = 0; + for(Counter = 0; Counter < NumberOfColumns; Counter++) + if(((CLVColumn*)fColumnList.ItemAt(Counter))->fFlags & CLV_EXPANDER) + NumberOfExpanders++; + int32 SetfExpanderColumnTo = -1; + for(Counter = 0; Counter < NumberOfColumnsToAdd; Counter++) + { + CLVColumn* ThisColumn = (CLVColumn*)NewColumns->ItemAt(Counter); + if(ThisColumn->fFlags & CLV_EXPANDER) + { + NumberOfExpanders++; + if(ThisColumn->IsShown()) + SetfExpanderColumnTo = NumberOfColumns + Counter; + } + } + if(NumberOfExpanders != 0 && !fHierarchical) + return false; + if(NumberOfExpanders > 1) + return false; + if(SetfExpanderColumnTo != -1) + fExpanderColumn = SetfExpanderColumnTo; + + //Make sure none of these columns have already been added to a ColumnListView + for(Counter = 0; Counter < NumberOfColumnsToAdd; Counter++) + if(((CLVColumn*)NewColumns->ItemAt(Counter))->fParent != NULL) + return false; + //Make sure none of these columns are being added twice + for(Counter = 0; Counter < NumberOfColumnsToAdd-1; Counter++) + for(int32 Counter2 = Counter+1; Counter2 < NumberOfColumnsToAdd; Counter2++) + if(NewColumns->ItemAt(Counter) == NewColumns->ItemAt(Counter2)) + return false; + + BWindow* ParentWindow = Window(); + if(ParentWindow) + ParentWindow->Lock(); + for(Counter = 0; Counter < NumberOfColumnsToAdd; Counter++) + { + CLVColumn* Column = (CLVColumn*)NewColumns->ItemAt(Counter); + //Check if this should be locked at the beginning or end, and adjust its position if necessary + int32 DisplayIndex = NumberOfColumns; + if(!Column->Flags() & CLV_LOCK_AT_END) + { + bool Repeat; + if(Column->Flags() & CLV_LOCK_AT_BEGINNING) + { + //Move it to the beginning, after the last CLV_LOCK_AT_BEGINNING item + DisplayIndex = 0; + Repeat = true; + while(Repeat && DisplayIndex < NumberOfColumns) + { + Repeat = false; + CLVColumn* LastColumn = (CLVColumn*)fColumnDisplayList.ItemAt(DisplayIndex); + if(LastColumn->Flags() & CLV_LOCK_AT_BEGINNING) + { + DisplayIndex++; + Repeat = true; + } + } + } + else + { + //Make sure it isn't after a CLV_LOCK_AT_END item + Repeat = true; + while(Repeat && DisplayIndex > 0) + { + Repeat = false; + CLVColumn* LastColumn = (CLVColumn*)fColumnDisplayList.ItemAt(DisplayIndex-1); + if(LastColumn->Flags() & CLV_LOCK_AT_END) + { + DisplayIndex--; + Repeat = true; + } + } + } + } + + //Add the column to the display list in the appropriate position + fColumnDisplayList.AddItem(Column, DisplayIndex); + + //Tell the column it belongs to me now + Column->fParent = this; + + NumberOfColumns++; + } + + //Add the columns to the end of the column list + fColumnList.AddList(NewColumns); + + //Set the scroll bars and tell views to update + ColumnsChanged(); + if(ParentWindow) + ParentWindow->Unlock(); + return true; +} + + +bool ColumnListView::RemoveColumn(CLVColumn* Column) +//Removes a CLVColumn from the ColumnListView. Returns true if successful. +{ + if(!fColumnList.HasItem(Column)) + return false; + int32 ColumnIndex = fSortKeyList.IndexOf(Column); + if(ColumnIndex >= 0) + fSortKeyList.RemoveItem(ColumnIndex); + + if(Column->fFlags & CLV_EXPANDER) + fExpanderColumn = -1; + + BWindow* ParentWindow = Window(); + if(ParentWindow) + ParentWindow->Lock(); + //Remove Column from the column and display lists + fColumnDisplayList.RemoveItem(Column); + fColumnList.RemoveItem(Column); + + //Tell the column it has been removed + Column->fParent = NULL; + + //Set the scroll bars and tell views to update + ColumnsChanged(); + if(ParentWindow) + ParentWindow->Unlock(); + return true; +} + + +bool ColumnListView::RemoveColumns(CLVColumn* Column, int32 Count) +//Finds Column in ColumnList and removes Count columns and their data from the view and its items +{ + BWindow* ParentWindow = Window(); + if(ParentWindow) + ParentWindow->Lock(); + int32 ColumnIndex = fColumnList.IndexOf(Column); + if(ColumnIndex < 0) + { + if(ParentWindow) + ParentWindow->Unlock(); + return false; + } + if(ColumnIndex + Count >= fColumnList.CountItems()) + { + if(ParentWindow) + ParentWindow->Unlock(); + return false; + } + + //Remove columns from the column and display lists + for(int32 Counter = ColumnIndex; Counter < ColumnIndex+Count; Counter++) + { + CLVColumn* ThisColumn = (CLVColumn*)fColumnList.ItemAt(Counter); + fColumnDisplayList.RemoveItem(ThisColumn); + + int32 SortIndex = fSortKeyList.IndexOf(Column); + if(SortIndex >= 0) + fSortKeyList.RemoveItem(SortIndex); + + if(ThisColumn->fFlags & CLV_EXPANDER) + fExpanderColumn = -1; + + //Tell the column it has been removed + ThisColumn->fParent = NULL; + } + fColumnList.RemoveItems(ColumnIndex,Count); + + //Set the scroll bars and tell views to update + ColumnsChanged(); + if(ParentWindow) + ParentWindow->Unlock(); + return true; +} + +void ColumnListView :: SetEditMessage(BMessage * newMsg, BMessenger target) +{ + delete _editMessage; + _editMessage = newMsg; + _editTarget = target; +} + +void ColumnListView :: KeyDown(const char * bytes, int32 numBytes) +{ + int colDiff = 0; + bool metaKeysPressed = false; + + // Find out if any meta-keys are pressed + int32 q; + if (Window()->CurrentMessage()->FindInt32("modifiers", &q) == B_NO_ERROR) + { + metaKeysPressed = ((q & (B_SHIFT_KEY | B_COMMAND_KEY | B_CONTROL_KEY | B_OPTION_KEY)) != 0); + } + + if (numBytes > 0) + { + switch (*bytes) + { + case B_LEFT_ARROW: + if (metaKeysPressed == false) + { + colDiff = -1; + break; + } + + case B_RIGHT_ARROW: + if (metaKeysPressed == false) + { + colDiff = 1; + break; + } + + case B_UP_ARROW: + case B_DOWN_ARROW: + if (metaKeysPressed == false) + { + BListView::KeyDown(bytes, numBytes); + break; + } + + default: + if (_editMessage != NULL) + { + BMessage temp(*_editMessage); + temp.AddInt32("column", _selectedColumn); + temp.AddInt32("row", CurrentSelection()); + temp.AddString("bytes", bytes); + + int32 key; + if (Window()->CurrentMessage()->FindInt32("key", &key) == B_NO_ERROR) temp.AddInt32("key", key); + + _editTarget.SendMessage(&temp); + } + break; + } + } + + if (colDiff != 0) + { + // We need to move the highlighted column by (colDiff) columns, if possible. + int numDisplayColumns = fColumnDisplayList.CountItems(); + + int curColumn = _selectedColumn; // curColumn is an ACTUAL index. + if (curColumn == -1) // no current column selected? + { + curColumn = (colDiff > 0) ? GetActualIndexOf(0) : GetActualIndexOf(numDisplayColumns-1); // go to an edge + } + else + { + // Go to the display column adjacent to the current column's display column. + int32 currentDisplayIndex = GetDisplayIndexOf(curColumn); + if (currentDisplayIndex < 0) currentDisplayIndex = 0; + currentDisplayIndex += colDiff; + + if (currentDisplayIndex < 0) currentDisplayIndex = numDisplayColumns - 1; + if (currentDisplayIndex >= numDisplayColumns) currentDisplayIndex = 0; + curColumn = GetActualIndexOf(currentDisplayIndex); + } + SetSelectedColumnIndex(curColumn); + } +} + +int32 ColumnListView::CountColumns() const +{ + return fColumnList.CountItems(); +} + + +int32 ColumnListView::IndexOfColumn(CLVColumn* column) const +{ + return fColumnList.IndexOf(column); +} + + +CLVColumn* ColumnListView::ColumnAt(int32 column_index) const +{ + return (CLVColumn*)fColumnList.ItemAt(column_index); +} + +CLVColumn* ColumnListView::ColumnAt(BPoint point) const +{ + for (int i=0; i= col->fColumnBegin)&&(point.x <= col->fColumnEnd)) return col; + } + return NULL; +} + +bool ColumnListView::SetDisplayOrder(const int32* ColumnOrder) +//Sets the display order using a BList of CLVColumn's +{ + BWindow* ParentWindow = Window(); + if(ParentWindow) + ParentWindow->Lock(); + //Add the items to the display list in order + fColumnDisplayList.MakeEmpty(); + int32 ColumnsToSet = fColumnList.CountItems(); + for(int32 Counter = 0; Counter < ColumnsToSet; Counter++) + { + if(ColumnOrder[Counter] >= ColumnsToSet) + { + if(ParentWindow) + ParentWindow->Unlock(); + return false; + } + for(int32 Counter2 = 0; Counter2 < Counter; Counter2++) + if(ColumnOrder[Counter] == ColumnOrder[Counter2]) + { + if(ParentWindow) + ParentWindow->Unlock(); + return false; + } + fColumnDisplayList.AddItem(fColumnList.ItemAt(ColumnOrder[Counter])); + } + + //Update everything about the columns + ColumnsChanged(); + + //Let the program know that the display order changed. + if(ParentWindow) + ParentWindow->Unlock(); + DisplayOrderChanged(ColumnOrder); + return true; +} + + +void ColumnListView::ColumnWidthChanged(int32 ColumnIndex, float NewWidth) +{ + Invalidate(); +} + + +void ColumnListView::DisplayOrderChanged(const int32* order) +{ } + + +int32* ColumnListView::DisplayOrder() const +{ + int32 ColumnsInList = fColumnList.CountItems(); + int32* ReturnList = new int32[ColumnsInList]; + BWindow* ParentWindow = Window(); + if(ParentWindow) + ParentWindow->Lock(); + for(int32 Counter = 0; Counter < ColumnsInList; Counter++) + ReturnList[Counter] = int32(fColumnList.IndexOf(fColumnDisplayList.ItemAt(Counter))); + if(ParentWindow) + ParentWindow->Unlock(); + return ReturnList; +} + + +void ColumnListView::SetSortKey(int32 ColumnIndex) +{ + CLVColumn* Column; + if(ColumnIndex >= 0) + { + Column = (CLVColumn*)fColumnList.ItemAt(ColumnIndex); + if(!(Column->Flags()&CLV_SORT_KEYABLE)) + return; + } + else + Column = NULL; + if(fSortKeyList.ItemAt(0) != Column || Column == NULL) + { + BWindow* ParentWindow = Window(); + if(ParentWindow) + ParentWindow->Lock(); + BRect LabelBounds = fColumnLabelView->Bounds(); + //Need to remove old sort keys and erase all the old underlines + int32 SortKeyCount = fSortKeyList.CountItems(); + for(int32 Counter = 0; Counter < SortKeyCount; Counter++) + { + CLVColumn* UnderlineColumn = (CLVColumn*)fSortKeyList.ItemAt(Counter); + if(UnderlineColumn->fSortMode != NoSort) + fColumnLabelView->Invalidate(BRect(UnderlineColumn->fColumnBegin,LabelBounds.top, + UnderlineColumn->fColumnEnd,LabelBounds.bottom)); + } + fSortKeyList.MakeEmpty(); + + if(Column) + { + fSortKeyList.AddItem(Column); + if(Column->fSortMode == NoSort) + SetSortMode(ColumnIndex,Ascending); + SortItems(); + //Need to draw new underline + fColumnLabelView->Invalidate(BRect(Column->fColumnBegin,LabelBounds.top,Column->fColumnEnd, + LabelBounds.bottom)); + } + if(ParentWindow) + ParentWindow->Unlock(); + } +} + + +void ColumnListView::AddSortKey(int32 ColumnIndex) +{ + CLVColumn* Column; + if(ColumnIndex >= 0) + { + Column = (CLVColumn*)fColumnList.ItemAt(ColumnIndex); + if(!(Column->Flags()&CLV_SORT_KEYABLE)) + return; + } + else + Column = NULL; + if(Column && !fSortKeyList.HasItem(Column)) + { + BWindow* ParentWindow = Window(); + if(ParentWindow) + ParentWindow->Lock(); + BRect LabelBounds = fColumnLabelView->Bounds(); + fSortKeyList.AddItem(Column); + if(Column->fSortMode == NoSort) + SetSortMode(ColumnIndex,Ascending); + SortItems(); + //Need to draw new underline + fColumnLabelView->Invalidate(BRect(Column->fColumnBegin,LabelBounds.top,Column->fColumnEnd, + LabelBounds.bottom)); + if(ParentWindow) + ParentWindow->Unlock(); + } +} + + +void ColumnListView::SetSortMode(int32 ColumnIndex,CLVSortMode Mode) +{ + CLVColumn* Column; + if(ColumnIndex >= 0) + { + Column = (CLVColumn*)fColumnList.ItemAt(ColumnIndex); + if(!(Column->Flags()&CLV_SORT_KEYABLE)) + return; + } + else + return; + if(Column->fSortMode != Mode) + { + BWindow* ParentWindow = Window(); + if(ParentWindow) + ParentWindow->Lock(); + BRect LabelBounds = fColumnLabelView->Bounds(); + Column->fSortMode = Mode; + if(Mode == NoSort && fSortKeyList.HasItem(Column)) + fSortKeyList.RemoveItem(Column); + SortItems(); + //Need to draw or erase underline + fColumnLabelView->Invalidate(BRect(Column->fColumnBegin,LabelBounds.top,Column->fColumnEnd, + LabelBounds.bottom)); + if(ParentWindow) + ParentWindow->Unlock(); + } +} + + +void ColumnListView::ReverseSortMode(int32 ColumnIndex) +{ + CLVColumn* Column; + if(ColumnIndex >= 0) + { + Column = (CLVColumn*)fColumnList.ItemAt(ColumnIndex); + if(!(Column->Flags()&CLV_SORT_KEYABLE)) + return; + } + else + return; + if(Column->fSortMode == Ascending) + SetSortMode(ColumnIndex,Descending); + else if(Column->fSortMode == Descending) + SetSortMode(ColumnIndex,NoSort); + else if(Column->fSortMode == NoSort) + SetSortMode(ColumnIndex,Ascending); +} + + +int32 ColumnListView::Sorting(int32* SortKeys, CLVSortMode* SortModes) const +{ + BWindow* ParentWindow = Window(); + if(ParentWindow) + ParentWindow->Lock(); + int32 NumberOfKeys = fSortKeyList.CountItems(); + for(int32 Counter = 0; Counter < NumberOfKeys; Counter++) + { + CLVColumn* Column = (CLVColumn*)fSortKeyList.ItemAt(Counter); + SortKeys[Counter] = IndexOfColumn(Column); + SortModes[Counter] = Column->SortMode(); + } + if(ParentWindow) + ParentWindow->Unlock(); + return NumberOfKeys; +} + +void ColumnListView :: Pulse() +{ + int32 curSel = CurrentSelection(); + if (curSel >= 0) + { + CLVListItem * item = (CLVListItem *) ItemAt(curSel); + item->Pulse(this); + } +} + +void ColumnListView::SetSorting(int32 NumberOfKeys, int32* SortKeys, CLVSortMode* SortModes) +{ + BWindow* ParentWindow = Window(); + if(ParentWindow) + ParentWindow->Lock(); + + //Need to remove old sort keys and erase all the old underlines + BRect LabelBounds = fColumnLabelView->Bounds(); + int32 SortKeyCount = fSortKeyList.CountItems(); + for(int32 Counter = 0; Counter < SortKeyCount; Counter++) + { + CLVColumn* UnderlineColumn = (CLVColumn*)fSortKeyList.ItemAt(Counter); + if(UnderlineColumn->fSortMode != NoSort) + fColumnLabelView->Invalidate(BRect(UnderlineColumn->fColumnBegin,LabelBounds.top, + UnderlineColumn->fColumnEnd,LabelBounds.bottom)); + } + fSortKeyList.MakeEmpty(); + + for(int32 Counter = 0; Counter < NumberOfKeys; Counter++) + { + if(Counter == 0) + SetSortKey(SortKeys[0]); + else + AddSortKey(SortKeys[Counter]); + SetSortMode(SortKeys[Counter],SortModes[Counter]); + } + + if(ParentWindow) + ParentWindow->Unlock(); +} + +void ColumnListView::FrameResized(float width, float height) +{ + UpdateColumnSizesDataRectSizeScrollBars(); + int32 NumberOfItems = CountItems(); + BFont Font; + GetFont(&Font); + for(uint32 Counter = 0; Counter < NumberOfItems; Counter++) + ItemAt(Counter)->Update(this,&Font); + BListView::FrameResized(width,height); +} + + +void ColumnListView::AttachedToWindow() +//Hack to work around app_server bug +{ + BListView::AttachedToWindow(); + UpdateColumnSizesDataRectSizeScrollBars(); +} + + +void ColumnListView::ScrollTo(BPoint point) +{ + BListView::ScrollTo(point); + fColumnLabelView->ScrollTo(BPoint(point.x,0.0)); +} + +int32 ColumnListView::GetActualIndexOf(int32 displayIndex) const +{ + if ((displayIndex < 0)||(displayIndex >= fColumnDisplayList.CountItems())) return -1; + return (int32) fColumnList.IndexOf(fColumnDisplayList.ItemAt(displayIndex)); +} + +int32 ColumnListView::GetDisplayIndexOf(int32 realIndex) const +{ + if ((realIndex < 0)||(realIndex >= fColumnList.CountItems())) return -1; + return (int32) fColumnDisplayList.IndexOf(fColumnList.ItemAt(realIndex)); +} + +// Set a new (actual) column index as the selected index. Call with arg -1 to unselect all. +// Gotta change the _selectedColumn on all entries. There is +// undoubtedly a more efficient way to do this! --jaf +void ColumnListView :: SetSelectedColumnIndex(int32 col) +{ + if (_selectedColumn != col) + { + _selectedColumn = col; + + int numRows = fFullItemList.CountItems(); + for (int j=0; j_selectedColumn = _selectedColumn; + + // Update current row if necessary. + int32 selectedIndex = CurrentSelection(); + if (selectedIndex != -1) InvalidateItem(selectedIndex); + } +} + + +void ColumnListView::MouseDown(BPoint point) +{ + int prevColumn = _selectedColumn; + int32 numberOfColumns = fColumnDisplayList.CountItems(); + float xleft = point.x; + for(int32 Counter = 0; Counter < numberOfColumns; Counter++) + { + CLVColumn* Column = (CLVColumn*)fColumnDisplayList.ItemAt(Counter); + if(Column->IsShown()) + { + if (xleft > 0) + { + xleft -= Column->Width(); + if (xleft <= 0) + { + SetSelectedColumnIndex(GetActualIndexOf(Counter)); + break; + } + } + } + } + int32 ItemIndex = IndexOf(point); + if(ItemIndex >= 0) + { + CLVListItem* ClickedItem = (CLVListItem*)BListView::ItemAt(ItemIndex); + if(ClickedItem->fSuperItem) + if(ClickedItem->fExpanderButtonRect.Contains(point)) + { + if(ClickedItem->IsExpanded()) + Collapse(ClickedItem); + else + Expand(ClickedItem); + return; + } + } + + + // If it's a right-click, hoist up the popup-menu + const char * selectedText = NULL; + CLVColumn * col = ColumnAt(_selectedColumn); + if (col) + { + BPopUpMenu * popup = col->GetPopup(); + if (popup) + { + BMessage * msg = Window()->CurrentMessage(); + int32 buttons; + if ((msg->FindInt32("buttons", &buttons) == B_NO_ERROR)&&(buttons == B_SECONDARY_MOUSE_BUTTON)) + { + BPoint where(point); + Select(IndexOf(where)); + ConvertToScreen(&where); + BMenuItem * result = popup->Go(where, false); + if (result) selectedText = result->Label(); + } + } + } + + int prevRow = CurrentSelection(); + BListView::MouseDown(point); + + int curRow = CurrentSelection(); + if ((_editMessage != NULL)&&((selectedText)||((_selectedColumn == prevColumn)&&(curRow == prevRow)))) + { + // Send mouse message... + BMessage temp(*_editMessage); + temp.AddInt32("column", _selectedColumn); + temp.AddInt32("row", CurrentSelection()); + if (selectedText) temp.AddString("text", selectedText); + else temp.AddInt32("mouseClick", 0); + _editTarget.SendMessage(&temp); + } +} + +bool ColumnListView::AddUnder(CLVListItem* item, CLVListItem* superitem) +{ + if(!fHierarchical) + return false; + + //Find the superitem in the full list and display list (if shown) + int32 SuperItemPos = fFullItemList.IndexOf(superitem); + if(SuperItemPos < 0) + return false; + uint32 SuperItemLevel = superitem->fOutlineLevel; + + //Add the item under the superitem in the full list + int32 ItemPos = SuperItemPos + 1; + item->fOutlineLevel = SuperItemLevel + 1; + while(true) + { + CLVListItem* Temp = (CLVListItem*)fFullItemList.ItemAt(ItemPos); + if(Temp) + { + if(Temp->fOutlineLevel > SuperItemLevel) + ItemPos++; + else + break; + } + else + break; + } + return AddItemPrivate(item,ItemPos); +} + + +bool ColumnListView::AddItem(CLVListItem* item, int32 fullListIndex) +{ + return AddItemPrivate(item,fullListIndex); +} + + +bool ColumnListView::AddItem(CLVListItem* item) +{ + if(fHierarchical) + return AddItemPrivate(item,fFullItemList.CountItems()); + else + return AddItemPrivate(item,CountItems()); +} + + +bool ColumnListView::AddItemPrivate(CLVListItem* item, int32 fullListIndex) +{ + item->_selectedColumn = _selectedColumn; + + if(fHierarchical) + { + uint32 ItemLevel = item->OutlineLevel(); + + //Figure out whether it is visible (should it be added to visible list) + bool Visible = true; + + //Find the item that contains it in the full list + int32 SuperItemPos; + if(ItemLevel == 0) + SuperItemPos = -1; + else + SuperItemPos = fullListIndex - 1; + CLVListItem* SuperItem; + while(SuperItemPos >= 0) + { + SuperItem = (CLVListItem*)fFullItemList.ItemAt(SuperItemPos); + if(SuperItem) + { + if(SuperItem->fOutlineLevel >= ItemLevel) + SuperItemPos--; + else + break; + } + else + return false; + } + if(SuperItemPos >= 0 && SuperItem) + { + if(!SuperItem->IsExpanded()) + //SuperItem's contents aren't visible + Visible = false; + if(!HasItem(SuperItem)) + //SuperItem itself isn't showing + Visible = false; + } + + //Add the item to the full list + if(!fFullItemList.AddItem(item,fullListIndex)) + return false; + else + { + //Add the item to the display list + if(Visible) + { + //Find the previous item, or -1 if the item I'm adding will be the first one + int32 PreviousItemPos = fullListIndex - 1; + CLVListItem* PreviousItem; + while(PreviousItemPos >= 0) + { + PreviousItem = (CLVListItem*)fFullItemList.ItemAt(PreviousItemPos); + if(PreviousItem && HasItem(PreviousItem)) + break; + else + PreviousItemPos--; + } + + //Add the item after the previous item, or first on the list + bool Result; + if(PreviousItemPos >= 0) + Result = BListView::AddItem((BListItem*)item,IndexOf(PreviousItem)+1); + else + Result = BListView::AddItem((BListItem*)item,0); + if(Result == false) + fFullItemList.RemoveItem(item); + return Result; + } + return true; + } + } + else + return BListView::AddItem(item,fullListIndex); +} + + +bool ColumnListView::AddList(BList* newItems) +{ + if(fHierarchical) + return AddListPrivate(newItems,fFullItemList.CountItems()); + else + return AddListPrivate(newItems,CountItems()); +} + + +bool ColumnListView::AddList(BList* newItems, int32 fullListIndex) +{ + return AddListPrivate(newItems,fullListIndex); +} + + +bool ColumnListView::AddListPrivate(BList* newItems, int32 fullListIndex) +{ + int32 NumberOfItems = newItems->CountItems(); + for(int32 count = 0; count < NumberOfItems; count++) + if(!AddItemPrivate((CLVListItem*)newItems->ItemAt(count),fullListIndex+count)) + return false; + return true; +} + + +bool ColumnListView::RemoveItem(CLVListItem* item) +{ + if(item == NULL || !fFullItemList.HasItem(item)) + return false; + if(fHierarchical) + { + int32 ItemsToRemove = 1 + FullListNumberOfSubitems(item); + return RemoveItems(fFullItemList.IndexOf(item),ItemsToRemove); + } + else + return BListView::RemoveItem((BListItem*)item); +} + + +BListItem* ColumnListView::RemoveItem(int32 fullListIndex) +{ + if(fHierarchical) + { + CLVListItem* TheItem = (CLVListItem*)fFullItemList.ItemAt(fullListIndex); + if(TheItem) + { + int32 ItemsToRemove = 1 + FullListNumberOfSubitems(TheItem); + if(RemoveItems(fullListIndex,ItemsToRemove)) + return TheItem; + else + return NULL; + } + else + return NULL; + } + else + return BListView::RemoveItem(fullListIndex); +} + + +bool ColumnListView::RemoveItems(int32 fullListIndex, int32 count) +{ + CLVListItem* TheItem; + if(fHierarchical) + { + uint32 LastSuperItemLevel = ULONG_MAX; + int32 Counter; + int32 DisplayItemsToRemove = 0; + int32 FirstDisplayItemToRemove = -1; + for(Counter = fullListIndex; Counter < fullListIndex+count; Counter++) + { + TheItem = FullListItemAt(Counter); + if(TheItem->fOutlineLevel < LastSuperItemLevel) + LastSuperItemLevel = TheItem->fOutlineLevel; + if(BListView::HasItem((BListItem*)TheItem)) + { + DisplayItemsToRemove++; + if(FirstDisplayItemToRemove == -1) + FirstDisplayItemToRemove = BListView::IndexOf(TheItem); + } + } + while(true) + { + TheItem = FullListItemAt(Counter); + if(TheItem && TheItem->fOutlineLevel > LastSuperItemLevel) + { + count++; + Counter++; + if(BListView::HasItem((BListItem*)TheItem)) + { + DisplayItemsToRemove++; + if(FirstDisplayItemToRemove == -1) + FirstDisplayItemToRemove = BListView::IndexOf((BListItem*)TheItem); + } + } + else + break; + } + while(DisplayItemsToRemove > 0) + { + if(BListView::RemoveItem(FirstDisplayItemToRemove) == NULL) + return false; + DisplayItemsToRemove--; + } + return fFullItemList.RemoveItems(fullListIndex,count); + } + else + return BListView::RemoveItems(fullListIndex,count); +} + + +CLVListItem* ColumnListView::FullListItemAt(int32 fullListIndex) const +{ + return (CLVListItem*)fFullItemList.ItemAt(fullListIndex); +} + + +int32 ColumnListView::FullListIndexOf(const CLVListItem* item) const +{ + return fFullItemList.IndexOf((CLVListItem*)item); +} + + +int32 ColumnListView::FullListIndexOf(BPoint point) const +{ + int32 DisplayListIndex = IndexOf(point); + CLVListItem* TheItem = (CLVListItem*)ItemAt(DisplayListIndex); + if(TheItem) + return FullListIndexOf(TheItem); + else + return -1; +} + + +CLVListItem* ColumnListView::FullListFirstItem() const +{ + return (CLVListItem*)fFullItemList.FirstItem(); +} + + +CLVListItem* ColumnListView::FullListLastItem() const +{ + return (CLVListItem*)fFullItemList.LastItem(); +} + + +bool ColumnListView::FullListHasItem(const CLVListItem* item) const +{ + return fFullItemList.HasItem((CLVListItem*)item); +} + + +int32 ColumnListView::FullListCountItems() const +{ + return fFullItemList.CountItems(); +} + + +void ColumnListView::MakeEmpty() +{ + fFullItemList.MakeEmpty(); + BListView::MakeEmpty(); +} + + +void ColumnListView::MakeEmptyPrivate() +{ + fFullItemList.MakeEmpty(); + BListView::MakeEmpty(); +} + + +bool ColumnListView::FullListIsEmpty() const +{ + return fFullItemList.IsEmpty(); +} + + +int32 ColumnListView::FullListCurrentSelection(int32 index) const +{ + int32 Selection = CurrentSelection(index); + CLVListItem* SelectedItem = (CLVListItem*)ItemAt(Selection); + return FullListIndexOf(SelectedItem); +} + + +void ColumnListView::FullListDoForEach(bool (*func)(CLVListItem*)) +{ + int32 NumberOfItems = fFullItemList.CountItems(); + for(int32 Counter = 0; Counter < NumberOfItems; Counter++) + if(func((CLVListItem*)fFullItemList.ItemAt(Counter)) == true) + return; +} + + +void ColumnListView::FullListDoForEach(bool (*func)(CLVListItem*, void*), void* arg2) +{ + int32 NumberOfItems = fFullItemList.CountItems(); + for(int32 Counter = 0; Counter < NumberOfItems; Counter++) + if(func((CLVListItem*)fFullItemList.ItemAt(Counter),arg2) == true) + return; +} + + +CLVListItem* ColumnListView::Superitem(const CLVListItem* item) const +{ + int32 SuperItemPos; + uint32 ItemLevel = item->fOutlineLevel; + if(ItemLevel == 0) + SuperItemPos = -1; + else + SuperItemPos = fFullItemList.IndexOf((CLVListItem*)item) - 1; + CLVListItem* SuperItem; + while(SuperItemPos >= 0) + { + SuperItem = (CLVListItem*)fFullItemList.ItemAt(SuperItemPos); + if(SuperItem) + { + if(SuperItem->fOutlineLevel >= ItemLevel) + SuperItemPos--; + else + break; + } + else + return NULL; + } + if(SuperItemPos >= 0) + return SuperItem; + else + return NULL; +} + + +int32 ColumnListView::FullListNumberOfSubitems(const CLVListItem* item) const +{ + if(!fHierarchical) + return 0; + int32 ItemPos = FullListIndexOf(item); + int32 SubItemPos; + uint32 SuperItemLevel = item->fOutlineLevel; + if(ItemPos >= 0) + { + for(SubItemPos = ItemPos + 1; SubItemPos >= 1; SubItemPos++) + { + CLVListItem* TheItem = FullListItemAt(SubItemPos); + if(TheItem == NULL || TheItem->fOutlineLevel <= SuperItemLevel) + break; + } + } + else + return 0; + return SubItemPos-ItemPos-1; +} + + +void ColumnListView::Expand(CLVListItem* item) +{ + BWindow* ParentWindow = Window(); + if(ParentWindow) + ParentWindow->Lock(); + if(!(item->fSuperItem)) + item->fSuperItem = true; + if(item->IsExpanded()) + { + if(ParentWindow) + ParentWindow->Unlock(); + return; + } + item->SetExpanded(true); + if(!fHierarchical) + { + if(ParentWindow) + ParentWindow->Unlock(); + return; + } + + int32 DisplayIndex = IndexOf(item); + if(DisplayIndex >= 0) + { + if(fExpanderColumn >= 0) + { + //Change the state of the arrow + item->DrawItemColumn(this,item->fExpanderColumnRect,fExpanderColumn, (fExpanderColumn == _selectedColumn), true); + SetDrawingMode(B_OP_OVER); + DrawBitmap(&fDownArrow, BRect(0.0,0.0,item->fExpanderButtonRect.right- + item->fExpanderButtonRect.left,10.0),item->fExpanderButtonRect); + SetDrawingMode(B_OP_COPY); + } + + //Add the items under it + int32 FullListIndex = fFullItemList.IndexOf(item); + uint32 ItemLevel = item->fOutlineLevel; + int32 Counter = FullListIndex + 1; + int32 AddPos = DisplayIndex + 1; + while(true) + { + CLVListItem* NextItem = (CLVListItem*)fFullItemList.ItemAt(Counter); + if(NextItem == NULL) + break; + if(NextItem->fOutlineLevel > ItemLevel) + { + BListView::AddItem((BListItem*)NextItem,AddPos++); + if(NextItem->fSuperItem && !NextItem->IsExpanded()) + { + //The item I just added is collapsed, so skip all its children + uint32 SkipLevel = NextItem->fOutlineLevel + 1; + while(true) + { + Counter++; + NextItem = (CLVListItem*)fFullItemList.ItemAt(Counter); + if(NextItem == NULL) + break; + if(NextItem->fOutlineLevel < SkipLevel) + break; + } + } + else + Counter++; + } + else + break; + } + } + if(ParentWindow) + ParentWindow->Unlock(); +} + + +void ColumnListView::Collapse(CLVListItem* item) +{ + BWindow* ParentWindow = Window(); + if(ParentWindow) + ParentWindow->Lock(); + if(!(item->fSuperItem)) + item->fSuperItem = true; + if(!(item->IsExpanded())) + { + if(ParentWindow) + ParentWindow->Unlock(); + return; + } + item->SetExpanded(false); + if(!fHierarchical) + { + if(ParentWindow) + ParentWindow->Unlock(); + return; + } + + int32 DisplayIndex = IndexOf((BListItem*)item); + if(DisplayIndex >= 0) + { + if(fExpanderColumn >= 0) + { + //Change the state of the arrow + item->DrawItemColumn(this,item->fExpanderColumnRect,fExpanderColumn,(fExpanderColumn == _selectedColumn), true); + SetDrawingMode(B_OP_OVER); + DrawBitmap(&fRightArrow, BRect(0.0,0.0,item->fExpanderButtonRect.right- + item->fExpanderButtonRect.left,10.0),item->fExpanderButtonRect); + SetDrawingMode(B_OP_COPY); + } + + //Remove the items under it + int32 FullListIndex = fFullItemList.IndexOf(item); + uint32 ItemLevel = item->fOutlineLevel; + int32 NextItemIndex = DisplayIndex+1; + while(true) + { + CLVListItem* NextItem = (CLVListItem*)ItemAt(NextItemIndex); + if(NextItem) + { + if(NextItem->fOutlineLevel > ItemLevel) + BListView::RemoveItem(NextItemIndex); + else + break; + } + else + break; + } + } + if(ParentWindow) + ParentWindow->Unlock(); +} + + +bool ColumnListView::IsExpanded(int32 fullListIndex) const +{ + BListItem* TheItem = (BListItem*)fFullItemList.ItemAt(fullListIndex); + if(TheItem) + return TheItem->IsExpanded(); + else + return false; +} + + +void ColumnListView::SetSortFunction(CLVCompareFuncPtr compare) +{ + fCompare = compare; +} + + +void ColumnListView::SortItems() +{ + BWindow* ParentWindow = Window(); + if(ParentWindow) + ParentWindow->Lock(); + + BList NewList; + int32 NumberOfItems; + if(!fHierarchical) + NumberOfItems = CountItems(); + else + NumberOfItems = fFullItemList.CountItems(); + if(NumberOfItems == 0) + { + if(ParentWindow) + ParentWindow->Unlock(); + return; + } + int32 Counter; + BRect OldBounds; + if(!fHierarchical) + { + //Plain sort + //Remember the list context for each item + for(Counter = 0; Counter < NumberOfItems; Counter++) + ((CLVListItem*)ItemAt(Counter))->fSortingContextCLV = this; + //Do the actual sort + BListView::SortItems((int (*)(const void*, const void*))ColumnListView::PlainBListSortFunc); + } + else + { + //Block-by-block sort + SortFullListSegment(0,0,&NewList); + fFullItemList = NewList; + //Remember the list context for each item + for(Counter = 0; Counter < NumberOfItems; Counter++) + ((CLVListItem*)fFullItemList.ItemAt(Counter))->fSortingContextBList = &NewList; + //Do the actual sort + BListView::SortItems((int (*)(const void*, const void*))ColumnListView::HierarchicalBListSortFunc); + } + + if(ParentWindow) + ParentWindow->Unlock(); +} + + +int ColumnListView::PlainBListSortFunc(BListItem** a_item1, BListItem** a_item2) +{ + CLVListItem* item1 = (CLVListItem*)*a_item1; + CLVListItem* item2 = (CLVListItem*)*a_item2; + ColumnListView* SortingContext = item1->fSortingContextCLV; + int32 SortDepth = SortingContext->fSortKeyList.CountItems(); + int CompareResult = 0; + if(SortingContext->fCompare) + for(int32 SortIteration = 0; SortIteration < SortDepth && CompareResult == 0; SortIteration++) + { + CLVColumn* Column = (CLVColumn*)SortingContext->fSortKeyList.ItemAt(SortIteration); + CompareResult = SortingContext->fCompare(item1,item2,SortingContext->fColumnList.IndexOf(Column)); + if(Column->fSortMode == Descending) + CompareResult = 0-CompareResult; + } + return CompareResult; +} + + +int ColumnListView::HierarchicalBListSortFunc(BListItem** a_item1, BListItem** a_item2) +{ + CLVListItem* item1 = (CLVListItem*)*a_item1; + CLVListItem* item2 = (CLVListItem*)*a_item2; + if(item1->fSortingContextBList->IndexOf(item1) < item1->fSortingContextBList->IndexOf(item2)) + return -1; + else + return 1; +} + + +void ColumnListView::SortFullListSegment(int32 OriginalListStartIndex, int32 InsertionPoint, + BList* NewList) +{ + //Identify and sort the items at this level + BList* ItemsInThisLevel = SortItemsInThisLevel(OriginalListStartIndex); + int32 NewItemsStopIndex = InsertionPoint + ItemsInThisLevel->CountItems(); + NewList->AddList(ItemsInThisLevel,InsertionPoint); + delete ItemsInThisLevel; + + //Identify and sort the subitems + for(int32 Counter = InsertionPoint; Counter < NewItemsStopIndex; Counter++) + { + CLVListItem* ThisItem = (CLVListItem*)NewList->ItemAt(Counter); + CLVListItem* NextItem = (CLVListItem*)fFullItemList.ItemAt(fFullItemList.IndexOf(ThisItem)+1); + if(ThisItem->IsSuperItem() && NextItem && ThisItem->fOutlineLevel < NextItem->fOutlineLevel) + { + int32 OldListSize = NewList->CountItems(); + SortFullListSegment(fFullItemList.IndexOf(ThisItem)+1,Counter+1,NewList); + int32 NewListSize = NewList->CountItems(); + NewItemsStopIndex += NewListSize - OldListSize; + Counter += NewListSize - OldListSize; + } + } +} + + +BList* ColumnListView::SortItemsInThisLevel(int32 OriginalListStartIndex) +{ + uint32 ThisLevel = ((CLVListItem*)fFullItemList.ItemAt(OriginalListStartIndex))->fOutlineLevel; + + //Create a new BList of the items in this level + int32 Counter = OriginalListStartIndex; + int32 ItemsInThisLevel = 0; + BList* ThisLevelItems = new BList(16); + while(true) + { + CLVListItem* ThisItem = (CLVListItem*)fFullItemList.ItemAt(Counter); + if(ThisItem == NULL) + break; + uint32 ThisItemLevel = ThisItem->fOutlineLevel; + if(ThisItemLevel == ThisLevel) + { + ThisLevelItems->AddItem(ThisItem); + ItemsInThisLevel++; + } + else if(ThisItemLevel < ThisLevel) + break; + Counter++; + } + + //Sort the BList of the items in this level + CLVListItem** SortArray = new CLVListItem*[ItemsInThisLevel]; + CLVListItem** ListItems = (CLVListItem**)ThisLevelItems->Items(); + for(Counter = 0; Counter < ItemsInThisLevel; Counter++) + SortArray[Counter] = ListItems[Counter]; + ThisLevelItems->MakeEmpty(); + SortListArray(SortArray,ItemsInThisLevel); + for(Counter = 0; Counter < ItemsInThisLevel; Counter++) + ThisLevelItems->AddItem(SortArray[Counter]); + return ThisLevelItems; +} + + +void ColumnListView::SortListArray(CLVListItem** SortArray, int32 NumberOfItems) +{ + int32 SortDepth = fSortKeyList.CountItems(); + for(int32 Counter1 = 0; Counter1 < NumberOfItems-1; Counter1++) + for(int32 Counter2 = Counter1+1; Counter2 < NumberOfItems; Counter2++) + { + int CompareResult = 0; + if(fCompare) + for(int32 SortIteration = 0; SortIteration < SortDepth && CompareResult == 0; SortIteration++) + { + CLVColumn* Column = (CLVColumn*)fSortKeyList.ItemAt(SortIteration); + CompareResult = fCompare(SortArray[Counter1],SortArray[Counter2],fColumnList.IndexOf(Column)); + if(Column->fSortMode == Descending) + CompareResult = 0-CompareResult; + } + if(CompareResult == 1) + { + CLVListItem* Temp = SortArray[Counter1]; + SortArray[Counter1] = SortArray[Counter2]; + SortArray[Counter2] = Temp; + } + } +} + + +void ColumnListView :: MessageReceived(BMessage * msg) +{ + switch(msg->what) + { + case B_UNMAPPED_KEY_DOWN: + if (_editMessage != NULL) + { + BMessage temp(*_editMessage); + temp.AddInt32("column", _selectedColumn); + temp.AddInt32("row", CurrentSelection()); + + int32 key; + if (msg->FindInt32("key", &key) == B_NO_ERROR) temp.AddInt32("unmappedkey", key); + _editTarget.SendMessage(&temp); + } + break; + + default: + BListView::MessageReceived(msg); + break; + } +} diff --git a/src/preferences/shortcuts/clv/ColumnListView.h b/src/preferences/shortcuts/clv/ColumnListView.h new file mode 100644 index 0000000000..45826792fa --- /dev/null +++ b/src/preferences/shortcuts/clv/ColumnListView.h @@ -0,0 +1,201 @@ +#ifndef ColumnListView_h +#define ColumnListView_h + +//Column list view header file + +//****************************************************************************************************** +//**** PROJECT HEADER FILES AND CLASS NAME DECLARATIONS +//****************************************************************************************************** + +#include + +#include "Colors.h" +#include "CLVColumn.h" +class CLVListItem; +class CLVColumnLabelView; +class CLVFillerView; +class CLVContainerView; +#include "PrefilledBitmap.h" +#include "ScrollViewCorner.h" + + +//****************************************************************************************************** +//**** CONSTANTS AND TYPE DEFINITIONS +//****************************************************************************************************** +typedef int (*CLVCompareFuncPtr)(const CLVListItem* item1, const CLVListItem* item2, int32 sort_key); + +//****************************************************************************************************** +//**** ColumnListView CLASS DECLARATION +//****************************************************************************************************** +class ColumnListView : public BListView +{ + public: + //Constructor and destructor + ColumnListView( BRect Frame, + BScrollView** ContainerView, //Used to get back a pointer to the container + //view that will hold the ColumnListView, the + //the CLVColumnLabelView, and the scrollbars. + //If no scroll bars or border are asked for, + //this will act like a plain BView container. + const char* Name = NULL, + uint32 ResizingMode = B_FOLLOW_LEFT | B_FOLLOW_TOP, + uint32 flags = B_WILL_DRAW | B_FRAME_EVENTS | B_NAVIGABLE, + list_view_type Type = B_SINGLE_SELECTION_LIST, + bool hierarchical = false, + bool horizontal = true, //Which scroll bars should I add, if any + bool vertical = true, + border_style border = B_NO_BORDER, //What type of border to add, if any + const BFont* LabelFont = be_plain_font); + virtual ~ColumnListView(); + void AddScrollViewCorner(); + + //Archival stuff + /*** Not implemented yet + ColumnListView(BMessage* archive); + static ColumnListView* Instantiate(BMessage* data); + virtual status_t Archive(BMessage* data, bool deep = true) const; + ***/ + + virtual void MessageReceived(BMessage * msg); + + //Column setup functions + virtual bool AddColumn(CLVColumn* Column); //Note that a column may only be added to + //one ColumnListView at a time, and may not + //be added more than once to the same + //ColumnListView without removing it + //inbetween + virtual bool AddColumnList(BList* NewColumns); + virtual bool RemoveColumn(CLVColumn* Column); + virtual bool RemoveColumns(CLVColumn* Column, int32 Count); //Finds Column in ColumnList + //and removes Count columns and + //their data from the view + //and its items + int32 CountColumns() const; + int32 IndexOfColumn(CLVColumn* column) const; + CLVColumn * ColumnAt(BPoint point) const; // Returns column located at point on screen --added by jaf + CLVColumn* ColumnAt(int32 column_index) const; + virtual bool SetDisplayOrder(const int32* Order); + //Sets the display order: each int32 in the Order list specifies the column index of the + //next column to display. Note that this DOES NOT get called if the user drags a + //column, so overriding it will not inform you of user changes. If you need that info, + //override DisplayOrderChanged instead. Also note that SetDisplayOrder does call + //DisplayOrderChanged(false). + virtual void ColumnWidthChanged(int32 ColumnIndex, float NewWidth); + virtual void DisplayOrderChanged(const int32* order); + //Override this if you want to find out when the display order changed. + int32* DisplayOrder() const; //Gets the display order in the same format as that used by + //SetDisplayOrder. The returned array belongs to the caller and + //must be delete[]'d when done with it. + virtual void SetSortKey(int32 ColumnIndex); + //Set it to -1 to remove the sort key. + virtual void AddSortKey(int32 ColumnIndex); + void ReverseSortMode(int32 ColumnIndex); + virtual void SetSortMode(int32 ColumnIndex,CLVSortMode Mode); + int32 Sorting(int32* SortKeys, CLVSortMode* SortModes) const; + //Returns the number of used sort keys, and fills the provided arrays with the sort keys + //by column index and sort modes, in priority order. The pointers should point to an array + //int32 SortKeys[n], and an array CLVSortMode SortModes[n] where n is the number of sortable + //columns in the ColumnListView. Note: sorting will only occur if the key column is shown. + void SetSorting(int32 NumberOfKeys, int32* SortKeys, CLVSortMode* SortModes); + //Sets the sorting parameters using the same format returned by Sorting + + //BView overrides + virtual void FrameResized(float Width, float Height); + virtual void AttachedToWindow(); + virtual void ScrollTo(BPoint point); + virtual void MouseDown(BPoint point); + + //List functions + virtual bool AddUnder(CLVListItem*, CLVListItem* superitem); + virtual bool AddItem(CLVListItem*, int32 fullListIndex); + virtual bool AddItem(CLVListItem*); + virtual bool AddList(BList* newItems); //This must be a BList of + //CLVListItem*'s, NOT BListItem*'s + virtual bool AddList(BList* newItems, int32 fullListIndex); //This must be a BList of + //CLVListItem*'s, NOT BListItem*'s + virtual bool RemoveItem(CLVListItem* item); + virtual BListItem* RemoveItem(int32 fullListIndex); //Actually returns CLVListItem + virtual bool RemoveItems(int32 fullListIndex, int32 count); + virtual void MakeEmpty(); + CLVListItem* FullListItemAt(int32 fullListIndex) const; + int32 FullListIndexOf(const CLVListItem* item) const; + int32 FullListIndexOf(BPoint point) const; + CLVListItem* FullListFirstItem() const; + CLVListItem* FullListLastItem() const; + bool FullListHasItem(const CLVListItem* item) const; + int32 FullListCountItems() const; + bool FullListIsEmpty() const; + int32 FullListCurrentSelection(int32 index = 0) const; + void FullListDoForEach(bool (*func)(CLVListItem*)); + void FullListDoForEach(bool (*func)(CLVListItem*, void*), void* arg2); + CLVListItem* Superitem(const CLVListItem* item) const; + int32 FullListNumberOfSubitems(const CLVListItem* item) const; + virtual void Expand(CLVListItem* item); + virtual void Collapse(CLVListItem* item); + bool IsExpanded(int32 fullListIndex) const; + void SetSortFunction(CLVCompareFuncPtr compare); + void SortItems(); + + virtual void KeyDown(const char * bytes, int32 numBytes); + + void SetEditMessage(BMessage * newMsg, BMessenger target); + // Sets a BMessage that will be sent every time a key is pressed, or the mouse + // is clicked in the active cell. (newMsg) becomes property of this ColumnListView. + // Copies of (newMsg) will be sent to (target). + + virtual void Pulse(); + // Used to make the cursor blink on the string column... + + int32 GetSelectedColumn() const {return _selectedColumn;} + + private: + friend class CLVMainView; + friend class CLVColumn; + friend class CLVColumnLabelView; + friend class CLVListItem; + + int32 GetActualIndexOf(int32 displayIndex) const; + // Returns the "real" index of the given display index, or -1 if there is none. + + int32 GetDisplayIndexOf(int32 actualIndex) const; + // Returns the display index of the given "real" index, or -1 if there is none. + + void SetSelectedColumnIndex(int32 selectedColumnIndex); + // Call this to change _selectedColumn to a new value properly. + + void UpdateColumnSizesDataRectSizeScrollBars(); + void UpdateScrollBars(); + void ColumnsChanged(); + void CreateContainer(bool horizontal, bool vertical, border_style border, uint32 ResizingMode, + uint32 flags); + void SortListArray(CLVListItem** SortArray, int32 NumberOfItems); + void MakeEmptyPrivate(); + bool AddListPrivate(BList* newItems, int32 fullListIndex); + bool AddItemPrivate(CLVListItem* item, int32 fullListIndex); + void SortFullListSegment(int32 OriginalListStartIndex, int32 InsertionPoint, BList* NewList); + BList* SortItemsInThisLevel(int32 OriginalListStartIndex); + static int PlainBListSortFunc(BListItem** item1, BListItem** item2); + static int HierarchicalBListSortFunc(BListItem** item1, BListItem** item2); + + CLVColumnLabelView* fColumnLabelView; + CLVContainerView* fScrollView; + ScrollViewCorner* fFillerView; + bool fHierarchical; + BList fColumnList; + BList fColumnDisplayList; + float fDataWidth,fDataHeight,fPageWidth,fPageHeight; + BList fSortKeyList; //List contains CLVColumn pointers + PrefilledBitmap fRightArrow; + PrefilledBitmap fDownArrow; + BList fFullItemList; + int32 fExpanderColumn; + CLVCompareFuncPtr fCompare; + + // added by jaf + int32 _selectedColumn; // actual index of the column that contains the active cell. + BMessage * _editMessage; // if non-NULL, sent on keypress or when active cell is clicked. + BMessenger _editTarget; // target for _editMessage. +}; + +#endif + diff --git a/src/preferences/shortcuts/clv/MouseWatcher.cpp b/src/preferences/shortcuts/clv/MouseWatcher.cpp new file mode 100644 index 0000000000..ebce54d478 --- /dev/null +++ b/src/preferences/shortcuts/clv/MouseWatcher.cpp @@ -0,0 +1,72 @@ +#include "MouseWatcher.h" + +#include +#include + +int32 MouseWatcher(void* data); + + +thread_id StartMouseWatcher(BView* TargetView) +{ + thread_id MouseWatcherThread = spawn_thread(MouseWatcher,"MouseWatcher",B_NORMAL_PRIORITY, + new BMessenger(TargetView)); + if(MouseWatcherThread != B_NO_MORE_THREADS && MouseWatcherThread != B_NO_MEMORY) + resume_thread(MouseWatcherThread); + return MouseWatcherThread; +} + + +int32 MouseWatcher(void* data) +{ + BMessenger* TheMessenger = (BMessenger*)data; + BPoint PreviousPos; + uint32 PreviousButtons; + bool FirstCheck = true; + BMessage MessageToSend; + MessageToSend.AddPoint("where",BPoint(0,0)); + MessageToSend.AddInt32("buttons",0); + MessageToSend.AddInt32("modifiers",0); + while(true) + { + if (!TheMessenger->LockTarget()) + { + delete TheMessenger; + return 0; // window is dead so exit + } + BLooper *TheLooper; + BView* TheView = (BView*)TheMessenger->Target(&TheLooper); + BPoint Where; + uint32 Buttons; + TheView->GetMouse(&Where,&Buttons,false); + if(FirstCheck) + { + PreviousPos = Where; + PreviousButtons = Buttons; + FirstCheck = false; + } + bool Send = false; + if(Buttons != PreviousButtons || Buttons == 0 || Where != PreviousPos) + { + if(Buttons == 0) + MessageToSend.what = MW_MOUSE_UP; + else if(Buttons != PreviousButtons) + MessageToSend.what = MW_MOUSE_DOWN; + else + MessageToSend.what = MW_MOUSE_MOVED; + MessageToSend.ReplacePoint("where",Where); + MessageToSend.ReplaceInt32("buttons",Buttons); + MessageToSend.ReplaceInt32("modifiers",modifiers()); + Send = true; + } + TheLooper->Unlock(); + if(Send) + TheMessenger->SendMessage(&MessageToSend); + if(Buttons == 0) + { + //Button was released + delete TheMessenger; + return 0; + } + snooze(50000); + } +} diff --git a/src/preferences/shortcuts/clv/MouseWatcher.h b/src/preferences/shortcuts/clv/MouseWatcher.h new file mode 100644 index 0000000000..8bfde210e5 --- /dev/null +++ b/src/preferences/shortcuts/clv/MouseWatcher.h @@ -0,0 +1,39 @@ +/****DOCUMENTATION +Once started, MouseWatcher will watch the mouse until the mouse buttons are all released, sending +messages to the target BView (TargetView is specified as the target handler in the BMessenger used to +send the messages. The BLooper == window of the target view is determined automatically by the +BMessenger) + +If the mouse moves, a MW_MOUSE_MOVED message is sent. +If the mouse buttons are changed, but not released, a MW_MOUSE_DOWN message is sent. +If the mouse button(s) are released, a MW_MOUSE_UP message is sent. + +These messages will have three data entries: + +"where" (B_POINT_TYPE) - The position of the mouse in TargetView's coordinate system. +"buttons" (B_INT32_TYPE) - The mouse buttons. See BView::GetMouse(). +"modifiers" (B_INT32_TYPE) - The modifier keys held down at the time. See modifiers(). + +Once it is started, you can't stop it, but that shouldn't matter - the user will most likely release +the buttons soon, and you can interpret the events however you want. + +StartMouseWatcher returns a valid thread ID, or it returns an error code: +B_NO_MORE_THREADS. all thread_id numbers are currently in use. +B_NO_MEMORY. Not enough memory to allocate the resources for another thread. +****/ +#ifndef MouseWatcher_h +#define MouseWatcher_h + +#include +#include + +class BView; + +const uint32 MW_MOUSE_DOWN = 'Mw-D'; +const uint32 MW_MOUSE_UP = 'Mw-U'; +const uint32 MW_MOUSE_MOVED = 'Mw-M'; + +thread_id StartMouseWatcher(BView* TargetView); + +#endif + diff --git a/src/preferences/shortcuts/clv/PrefilledBitmap.cpp b/src/preferences/shortcuts/clv/PrefilledBitmap.cpp new file mode 100644 index 0000000000..00f377de69 --- /dev/null +++ b/src/preferences/shortcuts/clv/PrefilledBitmap.cpp @@ -0,0 +1,14 @@ +#include "PrefilledBitmap.h" + +PrefilledBitmap::PrefilledBitmap(BRect bounds, color_space space, const void *data, bool acceptsViews, + bool needsContiguousMemory) +: BBitmap(bounds, space, acceptsViews, needsContiguousMemory) +{ + int32 length = ((int32(bounds.right-bounds.left)+3) / 4) * 4; + length *= int32(bounds.bottom-bounds.top)+1; + SetBits(data, length, 0, space); +} + + +PrefilledBitmap::~PrefilledBitmap() +{ } \ No newline at end of file diff --git a/src/preferences/shortcuts/clv/PrefilledBitmap.h b/src/preferences/shortcuts/clv/PrefilledBitmap.h new file mode 100644 index 0000000000..eaf521ddd4 --- /dev/null +++ b/src/preferences/shortcuts/clv/PrefilledBitmap.h @@ -0,0 +1,18 @@ +#ifndef PreFilledBitmap_h +#define PreFilledBitmap_h + +#include + +//Useful until be implements BBitmap::BBitmap(BRect bounds, color_space space, const void *data, +// bool acceptsViews, bool needsContiguousMemory) +//or something like it... + +class PrefilledBitmap : public BBitmap +{ + public: + PrefilledBitmap(BRect bounds, color_space space, const void *data, bool acceptsViews, + bool needsContiguousMemory); + ~PrefilledBitmap(); +}; + +#endif \ No newline at end of file diff --git a/src/preferences/shortcuts/clv/ScrollViewCorner.cpp b/src/preferences/shortcuts/clv/ScrollViewCorner.cpp new file mode 100644 index 0000000000..3ada5390c7 --- /dev/null +++ b/src/preferences/shortcuts/clv/ScrollViewCorner.cpp @@ -0,0 +1,28 @@ +#include "Colors.h" +#include "ScrollViewCorner.h" + +#include + +ScrollViewCorner::ScrollViewCorner(float Left,float Top) +: BView(BRect(Left,Top,Left+B_V_SCROLL_BAR_WIDTH,Top+B_H_SCROLL_BAR_HEIGHT),NULL,B_FOLLOW_RIGHT | + B_FOLLOW_BOTTOM,B_WILL_DRAW) +{ + SetHighColor(BeShadow); + SetViewColor(BeInactiveGrey); +} + + +ScrollViewCorner::~ScrollViewCorner() +{ } + + +void ScrollViewCorner::Draw(BRect Update) +{ + if(Update.bottom >= B_H_SCROLL_BAR_HEIGHT) + StrokeLine(BPoint(0.0,B_H_SCROLL_BAR_HEIGHT),BPoint(B_V_SCROLL_BAR_WIDTH,B_H_SCROLL_BAR_HEIGHT)); + if(Update.right >= B_V_SCROLL_BAR_WIDTH) + StrokeLine(BPoint(B_V_SCROLL_BAR_WIDTH,0.0), + BPoint(B_V_SCROLL_BAR_WIDTH,B_H_SCROLL_BAR_HEIGHT-1.0)); +} + + diff --git a/src/preferences/shortcuts/clv/ScrollViewCorner.h b/src/preferences/shortcuts/clv/ScrollViewCorner.h new file mode 100644 index 0000000000..6507294792 --- /dev/null +++ b/src/preferences/shortcuts/clv/ScrollViewCorner.h @@ -0,0 +1,21 @@ +#ifndef ScrollViewCorner_h +#define ScrollViewCorner_h + +//If you have a BScrollView with horizontal and vertical sliders that isn't +//seated to the lower-right corner of a B_DOCUMENT_WINDOW, there's a "hole" +//between the sliders that needs to be filled. You can use this to fill it. +//In general, it looks best to set the ScrollViewCorner color to +//BeInactiveControlGrey if the vertical BScrollBar is inactive, and the color +//to BeBackgroundGrey if the vertical BScrollBar is active. Have a look at +//Demo3 of ColumnListView to see what I mean if this is unclear. + + +class ScrollViewCorner : public BView +{ + public: + ScrollViewCorner(float Left,float Top); + ~ScrollViewCorner(); + void Draw(BRect Update); +}; + +#endif diff --git a/src/preferences/shortcuts/main.cpp b/src/preferences/shortcuts/main.cpp new file mode 100644 index 0000000000..cfffe7a3a4 --- /dev/null +++ b/src/preferences/shortcuts/main.cpp @@ -0,0 +1,18 @@ +/* + * Copyright 1999-2009 Haiku Inc. All rights reserved. + * Distributed under the terms of the MIT License. + * + * Authors: + * Jeremy Friesner + */ + + +#include "ShortcutsApp.h" +#include "KeyInfos.h" + +int main(int argc, char** argv) +{ + InitKeyIndices(); + (new ShortcutsApp)->Run(); + delete be_app; +}