Can't undo or redo actions that add a row to a NSOutlineView - delete-row

Most of my undo/redo actions have stopped working. I've written a simple example.
aNewNoteFor
successfully adds an OutlineItem to its sibling's parent's children and inserts it into the OutlineView row following its sibling.
deleteAChild:
is supposed to remove the row and delete it from its parent's children.
But neither undo or redo are enabled.
- (void)aNewNoteFor:(OutlineItem*) sibling
{
//Ignore note. it's unimportant
Note* note = [self.document makeDraft:YES];
OutlineItem* newChild = [[OutlineItem alloc]initWithNote:note];
NSInteger index = [sibling.parent.children indexOfObject:sibling] +1;
[sibling.parent.children insertObject:newChild atIndex:index];
newChild.parent = sibling.parent;
[_document updateChangeCount:NSChangeDone];
[_outlineOutlet reloadData];
[_undoManager registerUndoWithTarget:self selector:#selector(deleteAChild:) object:newChild];
}
-(void)deleteAChild: (OutlineItem*)child
{
OutlineItem* parent = child.parent;
[parent.children removeObject:child];
[_outlineOutlet reloadData];
[_undoManager registerUndoWithTarget:self selector:#selector(aNewNoteFor:) object:parent];
}
''''

Related

How to guarantee that I get the `HTREEITEM` for the item that the user right-clicked on in a CTreeControl

I have a window which has a CTreeCtrl. A user can right-click any element and display a context menu. From there they can choose to delete the entry. Something like this:
Here is a snippet of the context menu item handler:
void CAssignHistoryDlg::OnDeleteFromAssignmentHistory()
{
CString strINI = theApp.GetAssignHistoryPath();
HTREEITEM hItem = m_treeHistory.GetSelectedItem();
CString strName, strDeletedName, strEntry;
if (m_treeHistory.GetParentItem(hItem) != nullptr)
{
// The user has picked one of the history dates.
// So the parent should be the actual name.
hItem = m_treeHistory.GetParentItem(hItem);
// Now OK to proceed
}
strName = ExtractName(hItem);
GetParent()->EnableWindow(FALSE);
strEntry.Format(IDS_TPL_SURE_DELETE_FROM_ASSIGN_HIST, strName);
if (AfxMessageBox(strEntry, MB_YESNO | MB_ICONQUESTION) == IDNO)
{
The image shows my problem. If I first click on Test, so that it is selected and bright blue and then right-click, it shows Test in the popup message. This is fine. But ...
If the first name is initially selected, and I go ahead and directly right-click Test, even though it seems to go blue (as if selected), m_treeHistory.GetSelectedItem() is returning the original, first name. I think I am describing it not very well.
In short, I want to guarantee that I get the HTREEITEM for the item that the user right-clicked on. What I have is not 100% fool-proof.
If it helps, this is how I display the context menu:
void CAssignHistoryDlg::OnNMRclickTreeHistory(NMHDR *pNMHDR, LRESULT *pResult)
{
CMenu mnuContext, *pMnuEdit = nullptr;
CPoint ptLocal;
LPNMTREEVIEW pNMTreeView = reinterpret_cast<LPNMTREEVIEW>(pNMHDR);
GetCursorPos(&ptLocal);
mnuContext.LoadMenu( IDR_MENU_SM_ASSIGN_HIST_POPUP );
pMnuEdit = mnuContext.GetSubMenu( 0 );
if (pMnuEdit != nullptr)
{
pMnuEdit->TrackPopupMenu( TPM_LEFTALIGN|TPM_LEFTBUTTON,
ptLocal.x, ptLocal.y, this, nullptr );
}
*pResult = 0;
}
So to recap, at the moment the user must physically left click on a item in the tree to select it. Then they can right-click and it will offer to delete this person. But if they go ahead and just right-click on anyone, it will not offer to delete THAT person.
You can get the actual tree item at any given point using the HitTest() member of the CTreeCtrl class. Exactly how and where you do this will depend on your code design but, if you have a pointer to your CTreeCtrl (pwTree in the code below), then you can do something like this:
CPoint ptLocal;
GetCursorPos(&ptLocal);
pWTree->ScreenToClient(&ptLocal); // May not be required in certain handlers?
HTREEITEM hItem = pWTree->HitTest(ptLocal); // Remember to check for NULL return!
You can then either use the returned hItem directly, or use it to explicitly set the tree's selection (to that item), before doing any further processing.
The MS doc: https://learn.microsoft.com/en-us/cpp/mfc/reference/cwnd-class?view=vs-2019 doesn't have a "click" handler, it has CWnd::OnRButtonDblClk, CWnd::OnRButtonDown and CWnd::OnRButtonUp. Which one are you handling?
The reason for your defect might be that you don't let the tree control handle that right button event (to select that new item).
I would suggest to use CWnd::OnContextMenu instead.

how to avoid checkbox when we press space bar in tree control?

I am having a tree control where initially I had set it style in OnInitDialog as follows,
BOOL OnInitDialog()
{
CPropertyPage::OnInitDialog();
//Setting Treecontrol with TVS_CHECKBOXES style
HWND m_hTreeWnd = ::GetDlgItem(m_hWnd,IDC_TREE);
DWORD dwStyle = GetWindowLong(m_hTreeWnd,GWL_STYLE);
dwStyle |= (TVS_CHECKBOXES);
SetWindowLongPtr(m_hTreeWnd,GWL_STYLE,dwStyle);//CTreeCtrl m_hTreeWnd;
//Now I had initialized the tree control and I am want only few items of the tree to have
//checkboxes,In order to achieve that I did it as follows(Removing checkboxes where not required)
tvInsertItem.hParent = NULL;
tvInsertItem.hInsertAfter = TVI_ROOT;
tvInsertItem.item.mask = TVIF_TEXT|TVIF_IMAGE|TVIF_SELECTEDIMAGE;
tvInsertItem.item.pszText = L"Name"
hParentItemHandle = m_TreeCtrl.InsertItem(&tvInsertItem);
//Removal of checkboxes for the above item
tvItem.hItem = hParentItemHandle;
tvItem.mask = TVIF_TEXT|TVIF_STATE|TVIF_SELECTEDIMAGE;
tvItem.stateMask = TVIS_STATEIMAGEMASK;
tvItem.state = 0;
tvItem.pszText = szCommonModel;
m_TreeCtrl.SetItem(&tvItem);
}
Everything is fine I removed checkbox in which ever the node it is not required.And having checkbox where they are required.
All of a sudden I noticed this issue i.e, after selecting an item in the tree control which does not have checkbox ,now if
I press "space bar"then a checkbox is getting append to that item.
I want avoid the checkbox when I press space bar.
To avoid checkbox I tried this but did not work.
BOOL CTreeControlDlg::OnTvnItemChangingTree(UINT i,NMHDR *pNMHDR, LRESULT *pResult)
{
NMTVITEMCHANGE *pNMTVItemChange = reinterpret_cast<NMTVITEMCHANGE *>(pNMHDR);
HTREEITEM hTree = pNMTVItemChange->hItem;
UINT ChangItem = pNMTVItemChange->uStateNew;
UINT ChangItem1 = pNMTVItemChange->uStateOld;
UINT ItemState = m_TreeCtrl.GetItemState(hTree, TVIS_STATEIMAGEMASK);
if(98 == ItemState)//98 is the thing I observed while debugging this is not correct I know.
return FALSE;
return TRUE;
}
I am bit confused how do we get the state image mask TVIS_STATEIMGAEMASK and how do we make that check whether the item has this mask or not .
Can anyone please suggest me a way to acheive this.

Removing menu in MFC

In MFC how to remove a menu-item of POPUP type. RemoveMenu() either take ID or position. Since, there is no ID for POPUP menu, option left is by using position.
But I am not getting how and where I can call RemoveMenu().
File Edit Test
Test_submenu_1
Test_submenu_2
Test_submenu_3 > submenu_3_item_1
Test_submenu_4
Test_submenu_5
I want to remove Test_submenu_3? I am not getting how do find CMenu object for Test so that I can call RemoveMenu() by passing position "2" for submenu_3_item_1.
Any suggestion for doing this will be greatly appreciated.
Thanks!
You cannot use LoadMenu, since this function does just that.
After modifying loaded menu it is killed when menu object used to load it goes out of scope. You have to modify menu that is currently used.
Your menu is a popup part of the main menu, second in position. It contains 5 items and second one is another popup. To my understanding, you want to remove this item and popup of this item.
To make it work you will have to ask main window for the current menu:
CMenu* pMenu = GetMenu(); // get the main menu
CMenu* pPopupMenu = pMenu->GetSubMenu(2);//(Test menu with item....)
pPopupMenu->RemoveMenu(2, MF_BYPOSITION);
Of course, this code is from the main frame. If you want to use it elsewhere, you will have to access all using pointer to the main frame.
Try the below. You get the Test sub-menu first (index 2), then once you have that you tell it to remove its Test_submenu_3 submenu by position (also 2).
CMenu topMenu;
topMenu.LoadMenu(IDR_YOUR_MENU);
CMenu& testSubMenu = *topMenu.GetSubMenu(2);
testSubMenu.RemoveMenu(2,MF_BYPOSITION);
'Test' is the 3rd menu item (by position) on the top level menu. It's just been rendered horizontally rather than vertically. Assuming you have a handle to the top level menu use the same code you'd use to the get the sub menus as you would to get the 'Test' menu.
You can use this below code to remove the submenu, by comparing name
bool RemoveSubmenu(CMenu * pMenu) {
for (int pos = 0; pos < pMenu->GetMenuItemCount(); pos++) {
wchar_t *name = new wchar_t[mf.cch + 1];
MENUITEMINFO mf;
ZeroMemory(&mf, sizeof(mf));
mf.cbSize = sizeof(mf);
mf.fMask = MIIM_SUBMENU | MIIM_FTYPE | MIIM_STRING;
mf.fType = MIIM_STRING;
mf.dwTypeData = NULL;
if (!GetMenuItemInfo(pMenu->m_hMenu, pos, TRUE, &mf))
break;
if (mf.hSubMenu != NULL){
mf.fMask = MIIM_TYPE;
mf.fType = MFT_STRING;
++mf.cch;
mf.dwTypeData = (LPSTR)name;
if (!GetMenuItemInfo(pMenu->m_hMenu, pos, TRUE, &mf)){
bRet = false;
break;
}
//
// compare sub menu name (i.e mf.dwTypeData) here, do the required
// modifications
//
pMenu->RemoveMenu(pos, MF_BYPOSITION);
bRet = true;
break;
}
}
}

SVG - raphael: storing last path selected so element data can be changed

The current state
As from my link you can pick different regions on the map and everything seems to be working until you re-select a county you have selected before. Data values stored with each path decide if it isSelected or notSelected. I have no problem in changing the element data just clicked with this but I can't find a way of storing the last element selected in a way that I can change it's element data. Which means I first have to click on the previous county to set it's element data to notSelected
First I define var currentcountyselected = "";. This allows me to store the paths[arr[this.id]].name;. When I click on a new path I can make the last path fill change with $('#'+currentcountyselected).attr({fill: attributes.fill});
In Raphael's for loop I set obj.data('selected', 'notSelected'); so all path elements are set to notSeelected.
So what I need is some way to store the last path so I can change it's element data
This is the click function cleaned up from live example.
obj.click(function(){
if(this.data('selected') == 'notSelected')
{this.animate({fill: '#698B22' }, 300);
this.data('selected', 'isSelected');
$('#'+currentcountyselected).attr({fill: attributes.fill});
paths[arr[this.id]].value = "isSelected";
currentcountyselected = paths[arr[this.id]].name;
}
else
{this.animate({fill: '#32CD32'}, 300);
paths[arr[this.id]].value = "notSelected"; /* set path value*/
this.data('selected', 'notSelected');
}
});/* end mark selections */
I've been working on this project for a while and the client now wants the interface to work differently. This has really ate up my hourse.
EDIT:Although I have found a solution by simply taking out the if/else I would still like to know how to get at element data in a previous path (or any path for that matter).
Here is my solution, posted as it might help someone. The link in my question has problems with click happy users.
Globals
var previouscountyselected = "Mayo"; /* default start, can be any county(path) */
var start = true;
var past = null;
Changed code
obj.click(function(){
if(paths[arr[this.id]].value == 'notSelected')
{
this.animate({fill: '#698B22'}, 200);
paths[previouscountyselected].value = "notSelected";
paths[arr[this.id]].value = "isSelected";
previouscountyselected = paths[arr[this.id]].name;
if (!start && past != this)
{
past.animate({ fill: '#fff' }, 200);
}
past = this;
start = false;
}
else if(paths[arr[this.id]].value == 'isSelected')
{
this.animate({fill: '#32CD32'}, 200);
paths[arr[this.id]].value = "notSelected"; /* set path value */
}
});
Overview
if (!start && past != this) is a little unusual and is required or animated fades get messed up and choppy. The fade is not triggered if it is the first time a path is clicked and if you just hammer clicks on one path it doesn't fade to white. The main if/else handles the actual control value.
Until I get a jsfiddle up this link will demonstrate the desired behaviour.
Note! the drop menu in this link does not work.
Click happy friendly

How to add new admin menu items without reinstalling component in joomla?

I have been developing a component for Joomla 1.7.x, during development I need to add new component menu items to admin menu, it was easy by adding new rows to components table in DB in Joomla 1.5 times, but now it seems complicated to add a menu item by adding new row to menu table because of the database structure changes in Joomla 1.7
Is there a easy way to do this without reinstalling the component?
tHanks
The easiest way I found:
$table = JTable::getInstance('menu');
$data = array();
$data['menutype'] = 'main';
$data['client_id'] = 1;
$data['title'] = 'ITEM TITLE';
$data['alias'] = 'com-component-name';
$data['link'] = 'index.php?option=com_component_name&view=default';
$data['type'] = 'component';
$data['published'] = '0';
$data['parent_id'] = '117'; // ID, under which you want to add an item
$data['component_id'] = '10026'; // ID of the component
$data['img'] = 'components/com_component_name/assets/images/icon.png';
$data['home'] = 0;
if (
!$table->setLocation(117, 'last-child') // Parent ID, Position of an item
|| !$table->bind($data)
|| !$table->check()
|| !$table->store()
){
// Install failed, warn user and rollback changes
JError::raiseWarning(1, $table->getError());
return false;
}
To delete:
$table->delete(120); // item ID
$table->rebuild();
Based on http://docs.joomla.org/Using_nested_sets#Adding_a_new_node
Joomla 1.6+ menu items are stored under the #__menu table, with a special menu type called "main" for the admin menu.
Find your main component admin menu item's ID. You can add sub-items of that by declaring the parent_id column as the id of your main menu item, and setting the level column to 2.
The only other issue you are going to run into is the adoption of nested sets (lft and rgt columns). This is better way to handle parent-child relationships and the ordering of menu items. I'm unsure whether or not the parent_id or lft/rgt are used at this stage, but they are both filled in.
To add a new item, you would have to "shunt" all lft/rgt values by two for menu items with a value greater or equal to the position you wish to add your menu item. This should include the rgt of your parent item. If your parent item has no children, the lft for your new item will be the value of parent's left + 1. The value of the new item's rgt will be parent's lft + 2.
One thing to note with the lft and rgt is that the numbering applies to every menu item (front-end and back-end), so not doing it properly may have the potential to break your entire menu heirarchy. I think this is why the parent_id column is still used, and that there is an option in the admin area to "rebuild" the menu.
http://en.wikipedia.org/wiki/Nested_set_model
Here's a few SQL queries I came up with that did the trick (only the relevant parts shown):
SET #lastRgt := (SELECT rgt + 1 FROM #__menu WHERE alias="alias-of-preceding-menu-item");
UPDATE #__menu SET rgt=rgt+2 WHERE rgt > #lastRgt;
UPDATE #__menu SET lft=lft+2 WHERE lft > #lastRgt;
INSERT INTO #__menu (menutype, title, alias, path, link, type, published, parent_id, level, component_id, img, client_id, params, access, lft, rgt)
VALUES(..., #lastRgt+1, #lastRgt+2);
Worked for me on Joomla 2.5.
Admit's answer is in need of an update for Joomla 3.x
I'm sure it's correct for older Joomla versions which is why I'm not editing it.
This worked for me after further research and editing.
$table = JTableNested::getInstance('Menu');
$data = array();
$data['menutype'] = 'main';
$data['client_id'] = 1;
$data['title'] = 'ITEM TITLE';
$data['alias'] = 'com-component-name';
$data['link'] = 'index.php?option=com_component_name&view=default';
$data['type'] = 'component';
$data['published'] = '0';
$data['parent_id'] = '117'; // ID, under which you want to add an item
$data['component_id'] = '10026'; // ID of the component
$data['img'] = 'components/com_component_name/assets/images/icon.png';
$data['home'] = 0;
$table->setLocation(117, 'last-child') // Parent ID, Position of an item
if (!$table->bind($data) || !$table->check() || !$table->store()) {
// Install failed, warn user and rollback changes
JError::raiseWarning(1, $table->getError());
return false;
}

Resources