/* Copyright (c) 2017, Computer History Museum All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted (subject to the limitations in the disclaimer below) provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of Computer History Museum nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "nickwin.h" #include "listview.h" #define FILE_NUM 3 /* Copyright (c) 1990-1992 by the University of Illinois Board of Trustees */ /* Copyright (c) 1992 by Qualcomm, Inc. */ /********************************************************************** * handling the alias panel **********************************************************************/ // Some basic geometry #define kVertDividerWidth 2 #define kHorzZoomWidth 16 #define kApparentTabTitleSlop 12 #define kMinListWidth (180) #define kAquaHeightAdjustment 7 // slight height adjustment for Aqua #define kMinNickWinHeight 340+2*kAquaHeightAdjustment #define kTabWidthSlop 32 // Extra pixels to accomodate a min width for State and Zip fields // Tag delimeters #define kFieldTagBegin '<' #define kFieldTagEnd '>' #define kFieldTagSeparator ':' #define kDragDataDelim '\r' // First few items in the nickname list enum { kItemPersonalNicknames = 1, // The nickname for the user of this machine kItemEudoraNicknamesAB, // The Eudora Nicknames address book kItemNicknames // ev'r thang else }; // Icon constants for list view enum { kAddressBookIcon = kGenericFolderIconResource, kNicknameItem = MAILBOX_ONLY_ICON, kGroupItem = MAILBOX_ONLY_ICON }; // Control constants typedef enum { abViewByPopup = 0, abNewNicknameButton, abNewAddressBookButton, abRemoveButton, abToButton, abCcButton, abBccButton, abChatButton, abZoomHorizontalButton, abRecipientCheckbox, abNickField, abTabs, abDoNotSyncCheckbox, abControlCount } ABControlType; #pragma segment NickWin // Address book windows structure typedef struct { MyWindowPtr win; ViewList list; ControlHandle controls[abControlCount]; Handle oldRecipientMenu; Rect vertDivider; Rect listRect; Rect tabRect; Rect nickFieldRect; Str255 sortTag; NickFocusType nickFocus; long listFlags; short minTabWidth; short minListWidth; short collapsed; short currentTab; short abDisplayed; short nickDisplayed; Boolean tickleMe; Boolean inited; } ABType, *ABPtr, **ABHandle; ABType AB; short gSortAB; // Address book being sorted - used by our sort routines Boolean gPageOpened; // Some convenient shorthand #define Win AB.win #define NickList AB.list #define Controls AB.controls #define CtlViewBy AB.controls[abViewByPopup] #define CtlNewNickname AB.controls[abNewNicknameButton] #define CtlNewAddressBook AB.controls[abNewAddressBookButton] #define CtlRemove AB.controls[abRemoveButton] #define CtlTo AB.controls[abToButton] #define CtlCc AB.controls[abCcButton] #define CtlBcc AB.controls[abBccButton] #define CtlChat AB.controls[abChatButton] #define CtlZoomHorizontal AB.controls[abZoomHorizontalButton] #define CtlRecipient AB.controls[abRecipientCheckbox] #define CtlDoNotSync AB.controls[abDoNotSyncCheckbox] #define OldRecipientMenu AB.oldRecipientMenu #define NickField AB.controls[abNickField] #define Tabs AB.controls[abTabs] #define VertDivider AB.vertDivider #define ListRect AB.listRect #define TabRect AB.tabRect #define NickFieldRect AB.nickFieldRect #define SortTag AB.sortTag #define NickFocus AB.nickFocus #define ListFlags AB.listFlags #define MinTabWidth AB.minTabWidth #define MinListWidth AB.minListWidth #define CurrentTab AB.currentTab #define Collapsed AB.collapsed #define TickleMe AB.tickleMe typedef struct { PETEHandle pte; PETEParaInfo pi1; PETEParaInfo pi2; PETEParaInfo pi3; short ab; short nick; TextAddrHandle addresses; Str31 name; Boolean foundOne; ControlHandle tabPane; } NickPrintStuff, *NickPrintStuffPtr; typedef struct { Accumulator a; short refNum; OSErr theError; short ab; short nick; Str255 eol; Str255 comma; Str255 commaReplace; Str255 crReplace; } NickExportRec, *NickExportPtr, **NickExportHandle; Boolean ABHasSelection (MyWindowPtr win); Boolean ABIsChattable(void); OSErr ABChat(short modifiers); Boolean ABPrintPaneProc (ControlHandle tabControl, ControlHandle tabPane, short tabIndex, NickPrintStuffPtr myStuffP); Boolean ABPrintObjectProc (TabObjectPtr objectPtr, NickPrintStuffPtr myStuffP); OSErr PrintAddressBookWindow(Boolean select,Boolean now); OSErr PrintNickFile (short which, Boolean select, PETEHandle pte); void PrintNickname (NickPrintStuffPtr myStuffP, short ab, short nick); OSErr NickPage(short *pageNum,PStr title,Rect *uRect,short *v); void ABPrintMakeTitle(PStr title, Boolean select); void BuildNickPrintTitle(short which,Boolean select,PStr title); OSErr NickPrintName(PETEHandle pte, PETEParaInfoPtr pinfop, PStr name); OSErr NickPrintNameAndValue(PETEHandle pte, PETEParaInfoPtr pinfop, PStr name, PStr value); OSErr PrintANick(NickPrintStuffPtr myStuffP); OSErr NickPrintAddresses(PETEHandle pte, PETEParaInfoPtr pinfop, PStr name, UHandle value); Boolean TheWorldAccordingToMarthaStewart(MyWindowPtr win); void DoAddressBookExport (Boolean select); OSErr ExportNickFile (NickExportPtr nickExport, short ab, Boolean select); OSErr ExportNickname (NickExportPtr nickExport, short ab, short nick); Boolean ExportNicknameProc (TabObjectPtr objectPtr, NickExportPtr nickExport); pascal OSErr NickAddrSetDragContents(PETEHandle pte,DragReference drag); pascal OSErr NickGetDragContents(PETEHandle pte,UHandle *theText, PETEStyleListHandle *theStyles, PETEParaScrapHandle *theParas, DragReference drag, long dropLocation); // Stuff to eventually go away /* Information about user defined fields */ typedef struct { long height; // How many lines high? long width; // 1 = normal width, 2 = half width Str255 name; // Text to be displayed next to field Str255 tag; // Tag in the notes field Rect theRect; // Current field rectangle } fieldInfo; void GetABNick (VLNodeID nodeID, short *ab, short *nick) { *ab = GetAddressBook (nodeID); *nick = GetNickname (nodeID); } Boolean GetIndexedABNick (short index, short *ab, short *nick) { VLNodeInfo data; if (LVGetItem (&NickList, index, &data, false)) { GetABNick (data.nodeID, ab, nick); return (true); } *ab = kAddressBookUndefined; *nick = kNickUndefined; return (false); } Boolean GetSelectedABNick (short index, short *ab, short *nick) { VLNodeInfo data; if (LVGetItem (&NickList, index, &data, true)) { GetABNick (data.nodeID, ab, nick); return (true); } *ab = kAddressBookUndefined; *nick = kNickUndefined; return (false); } PStr GetSelectedName (short index, PStr name) { VLNodeInfo data; *name = 0; if (LVGetItem (&NickList, index, &data, true)) PCopy (name, data.name); return (name); } PStr GetSelectedABNickName (short index, short *ab, short *nick, PStr name) { VLNodeInfo data; *name = 0; if (LVGetItem (&NickList, index, &data, true)) { PCopy (name, data.name); GetABNick (data.nodeID, ab, nick); } return (name); } PStr GetSelectedABNickNameData (short index, short *ab, short *nick, PStr name, VLNodeInfo *data) { *name = 0; if (LVGetItem (&NickList, index, data, true)) { PCopy (name, data->name); GetABNick (data->nodeID, ab, nick); } return (name); } void OpenABWin (PStr findStr) { PETEDocInitInfo pdi; PETEHandle pte; TabObjectPtr objectPtr; WindowPtr WinWP; ControlHandle tabPane; Str255 title, tag; Rect r; OSErr theError; short tabIndex; WinWP = nil; theError = noErr; if (SelectOpenWazoo (ALIAS_WIN)) { // Do an initial find, if need be if (findStr) if (!FindNextNicknameContainingString(Win,findStr)) SysBeep(20L); return; // Already opened in a wazoo } // make sure the alias list is in memory if (RegenerateAllAliases ((MainEvent.modifiers & optionKey) != 0)) goto fail; // Clean up the recipient menu if we've been asked if (MainEvent.modifiers & optionKey) PruneRecipMenu (); if (AB.inited) UserSelectWindow (GetMyWindowWindowPtr (Win)); else { if (!(Win = GetNewMyWindow (ALIAS_WIND,nil,nil,BehindModal,False,False,ALIAS_WIN))) { theError = MemError (); goto fail; } // This needs to be initialized early so our close routine gets called if anything else fails Win->close = ABClose; // Initialize our private data AB.abDisplayed = kAddressBookUndefined; AB.nickDisplayed = kNickUndefined; AB.collapsed = GetPrefLong (PREF_NICK_COLLAPSED); WinWP = GetMyWindowWindowPtr (Win); // If anybody is wazooed with us, do not allow collapsing if (IsWazoo(WinWP) && !IsLonelyWazoo(WinWP)) AB.collapsed = false; SetPort_ (GetMyWindowCGrafPtr (Win)); ConfigFontSetup (Win); MySetThemeWindowBackground (Win, kThemeListViewBackgroundBrush, False); // controls if (!(CtlViewBy = CreateMenuControl(Win, nil, GetRString (title, VIEW_BY_LABEL), NICK_VIEW_BY_MENU, kControlPopupUseWFontVariant + kControlPopupVariableWidthVariant, 0, true)) || // View by menu !(CtlNewNickname = NewIconButton (AB_NEW_NICKNAME_CNTL, WinWP)) || // New Nickname button !(CtlNewAddressBook = NewIconButton (AB_NEW_ADDRESSBOOK_CNTL, WinWP)) || // New Address Book button !(CtlRemove = NewIconButton (AB_REMOVE_CNTL, WinWP)) || // Remove button !(CtlTo = CreateControl (Win, nil, HEADER_LABEL_STRN + TO_HEAD, kControlPushButtonProc, true)) || // To button !(CtlCc = CreateControl (Win, nil, HEADER_LABEL_STRN + CC_HEAD, kControlPushButtonProc, true)) || // Cc button !(CtlBcc = CreateControl (Win, nil, HEADER_LABEL_STRN + BCC_HEAD, kControlPushButtonProc, true)) || // Bcc button !(CtlChat = CreateControl (Win, nil, ICHAT_BTN_TEXT, kControlPushButtonProc, true)) || // Chat button !(CtlRecipient = CreateControl (Win, nil, ALIAS_ON_RECIPIENT_LIST, kControlCheckBoxProc, true)) || // Recipient checkbox !(CtlDoNotSync = CreateControl (Win, nil, ALIAS_DO_NOT_SYNC, kControlCheckBoxProc, true)) || // Do not sync checkbox !(CtlZoomHorizontal = GetNewControlSmall (NICK_HZOOM_CNTL, WinWP))) { // Horizontal zoom button theError = MemError (); goto fail; } SetControlMaximum (CtlRecipient, 2); SetControlMaximum (CtlDoNotSync, 2); OutlineControl (CtlTo, true); // (jp) For setting up our PETE's. DefaultPII (Win, false, peNoStyledPaste | peClearAllReturns, &pdi); // Label Fields NickField = CreateLabelField (Win, nil, GetRString (title, ALIAS_A_LABEL), 0, teForceLeft, labelAutoSize, &pdi, peNoStyledPaste | peClearAllReturns); CleanPII(&pdi); // Tabs (we don't know the min tab width at this point) SetRect (&r, 0, 0, 800, kMinNickWinHeight); if (Tabs = CreateTabControl (Win, &r, CREATOR, kNickTabType, false)) { tabIndex = 1; if (tabPane = FindTabPaneWithTitle (Tabs, GetPref (title, PREF_LAST_NICK_TAB))) tabIndex = GetTabPaneIndex (tabPane) + 1; SetControlValue (Tabs, tabIndex); CurrentTab = SwitchTabs (Tabs, 0, tabIndex, PREF_LAST_NICK_TAB); SetControlVisibility(Tabs,true,false); } // View By menu BuildViewByMenu (Tabs, CtlViewBy); // Nickname list ListFlags = kfListSupportsFocus | kfSupportsSelectCallbacks | kfSupportsMondoBigList | kfManualRowAddsExpected; SetRect (&r, -20, -20, 0, 0); if (LVNew (&NickList, Win, &r, kAdddressBookRootID, AddressBookLVCallBack, kLVDoOwnDragAdd)) { theError = MemError(); goto fail; } // The alias expansion field has special editing and drag capabilities, as does "other email" (dragging only for now) if (objectPtr = FindObjectWithTag (Tabs, GetRString (tag, ABReservedTagsStrn + abTagEmail))) { if (pte = GetLabelFieldPete (objectPtr->control)) { SetNickScanning (pte, nickHighlight | nickComplete | nickSpaces, kNickScanAllAliasFiles, nil, nil, nil); PETESetCallback (PETE, pte, (void *) NickAddrSetDragContents, peSetDragContents); PETESetCallback (PETE, pte, (void *) NickGetDragContents, peGetDragContents); } UnlockObject (objectPtr); } if (objectPtr = FindObjectWithTag (Tabs, GetRString (tag, ABReservedTagsStrn + abTagOtherEmail))) { if (pte = GetLabelFieldPete (objectPtr->control)) { PETESetCallback (PETE, pte, (void *) NickAddrSetDragContents, peSetDragContents); PETESetCallback (PETE, pte, (void *) NickGetDragContents, peGetDragContents); } UnlockObject (objectPtr); } // Set the windows minimum sizes once everything has been created MinListWidth = 2 * (2 * INSET) + kMinListWidth; MinTabWidth = 2 * (2 * INSET) + 8 + kTabWidthSlop; IterateTabPanes (Tabs, CalcMinTabWidthProc, &MinTabWidth); if (Collapsed) SetWinMinSize (Win, MinListWidth, kMinNickWinHeight); else SetWinMinSize (Win, MinListWidth + MinTabWidth, kMinNickWinHeight); // SetWinMinSize (Win, MinListWidth + MinTabWidth, kMinNickWinHeight); // Address book functions Win->didResize = ABDidResize; Win->update = ABUpdate; Win->position = PositionPrefsTitle; Win->click = ABClick; Win->bgClick = ABClick; Win->cursor = ABCursor; Win->activate = ABActivate; Win->help = ABHelp; Win->menu = ABMenu; Win->key = ABKey; Win->app1 = ABKey; Win->drag = ABDragHandler; Win->idle = ABIdle; Win->find = ABFind; Win->dirty = ABDirty; Win->selection = ABHasSelection; Win->menuEnable = ABMenuEnable; // Other initialization Win->dontControl = true; Win->userSave = true; // Save a copy of the recipient menu in case we make changes, then decide to discard if (OldRecipientMenu = GetResource('STR#',RECIPIENT_STRN)) MyHandToHand (&OldRecipientMenu); AB.inited = true; // clean all the dirty stuff so we're ready for user input ABClean (); // If we open in the collapsed state, hide the things on the right side if (Collapsed) { MoveLabelField (NickField, CNTL_OUT_OF_VIEW, CNTL_OUT_OF_VIEW, ControlWi (NickField), ControlHi (NickField)); MoveMyCntl (CtlRecipient, CNTL_OUT_OF_VIEW, CNTL_OUT_OF_VIEW, ControlWi (CtlRecipient), ControlHi (CtlRecipient)); MoveMyCntl (CtlDoNotSync, CNTL_OUT_OF_VIEW, CNTL_OUT_OF_VIEW, ControlWi (CtlDoNotSync), ControlHi (CtlDoNotSync)); MoveTab (Tabs, CNTL_OUT_OF_VIEW, CNTL_OUT_OF_VIEW, ControlWi (Tabs), ControlHi (Tabs)); } NickFocus = focusNoChange; ABSetKeyboardFocus (focusNickList); // Do an initial find, if need be if (findStr) if (!FindNextNicknameContainingString(Win,findStr)) SysBeep(20L); // Finally, show and size the window ShowMyWindow (WinWP); MyWindowDidResize (Win,&Win->contR); return; } fail: if (WinWP) CloseMyWindow (WinWP); if (theError) WarnUser (COULDNT_WIN, theError); } Boolean CalcMinTabWidthProc (ControlHandle tabControl, ControlHandle tabPane, short tabIndex, short *width) { MyWindowPtr win; ControlTabInfoRec tabInfo; Size actualSize; win = GetWindowMyWindowPtr(GetControlOwner(tabControl)); tabInfo.version = 0; if (!GetControlData (tabControl, tabIndex, kControlTabInfoTag, sizeof (ControlTabInfoRec), &tabInfo, &actualSize)) MinTabWidth += (2 * kApparentTabTitleSlop + win->hPitch * tabInfo.name[0]); return (false); } // // ABClean // // Make sure all the fields and UI dirty bits are ready for user input // void ABClean (void) { if (AB.inited) { PETEMarkDocDirty (PETE, GetLabelFieldPete (NickField), false); CleanTab (Tabs); Win->isDirty = false; } } /************************************************************************ * ABDidResize - resize the Address Book window ************************************************************************/ void ABDidResize (MyWindowPtr win, Rect *oldContR) { CGrafPtr winPort = GetMyWindowCGrafPtr (win); Rect rClipZero, r; short htAdjustment, buttonHeight, hTab1, // left side of list hTab2, // right side of list hTab3, // left side of vertical drag bar hTab4, // left side of content tab hTab5, // right side of content tab vTab1, // top of view by menu vTab2, // top of list vTab3, // bottom of list vTab4, // top of bevel buttons vTab5, // top of recipient buttons vTab6, // bottom of content tab vTab7, // top of the content tab winWidth, listWidth, nickPrct; // clip to nothing so we don't get controls leaving behind residues when they move SetPort (winPort); SetRect (&rClipZero, 0, 0, 0, 0); ClipRect (&rClipZero); // Figure out some basic geometry for the window buttonHeight = ControlHi (CtlCc); winWidth = RectWi (Win->contR); nickPrct = GetRLong (ALIAS_NICK_LIST_PRCT); if (Collapsed) { listWidth = winWidth - 4 * INSET; hTab1 = 2 * INSET; hTab2 = hTab1 + listWidth; hTab3 = Win->contR.right; hTab4 = hTab3; hTab5 = hTab3; } else { if (winWidth < win->minSize.h) winWidth = win->minSize.h; listWidth = winWidth * nickPrct / 100; if (listWidth < MinListWidth) listWidth = MinListWidth; if (winWidth - listWidth < MinTabWidth) listWidth = winWidth - MinTabWidth; hTab1 = 2 * INSET; hTab2 = hTab1 + listWidth; hTab3 = hTab2 + 2 * INSET; hTab4 = hTab3 + 2 * INSET; hTab5 = Win->contR.right - 2 * INSET; } vTab1 = Win->contR.top + INSET * 2; vTab2 = vTab1 + buttonHeight + INSET; vTab5 = Win->contR.bottom - 2 * INSET - buttonHeight; vTab3 = vTab5 - 2 * INSET - kHtCtl; vTab7 = vTab2; vTab6 = Win->contR.bottom - ControlHi (CtlDoNotSync) - 2 * INSET; // menu ButtonFit (CtlViewBy); MoveMyCntl (CtlViewBy, hTab1, vTab1, MIN (ControlWi (CtlViewBy), hTab2 - hTab1), 0); // list (we do this before the buttons because the list size may change SetRect (&r, hTab1, vTab2, hTab2, vTab3); LVSize (&NickList, &r, &htAdjustment); ListRect = NickList.bounds; vTab3 += htAdjustment; vTab4 = vTab3 + (vTab5 - vTab3 - kHtCtl) / 2; // buttons PositionBevelButtons (win, 3, &CtlNewNickname, hTab1, vTab4, kHtCtl, hTab3); PositionPushButtons (win, hTab1, hTab2, vTab5); // No need to reposition these when the window is horizontally collapsed if (!Collapsed) { // the vertical divider SetRect (&VertDivider, hTab3, vTab1, hTab3 + kVertDividerWidth, Win->contR.bottom - vTab1); // recipient checkbox MoveMyCntl (CtlRecipient, hTab5 - ControlWi (CtlRecipient), vTab1, 0, ONE_LINE_HI(win) ); // do not sync checkbox MoveMyCntl (CtlDoNotSync, hTab4, vTab6 + INSET, 0, ControlHi (CtlDoNotSync)); // the nickname field MoveLabelField (NickField, hTab4, vTab1, hTab5 - ControlWi (CtlRecipient) - 2 * INSET - hTab4, ONE_LINE_HI (win)); GetControlBounds (NickField, &NickFieldRect); // content pane MoveTab (Tabs, hTab4, vTab7, hTab5 - hTab4, vTab6 - vTab7 - kAquaHeightAdjustment); GetControlBounds (Tabs, &TabRect); } // Need to do this here to catch wazoo promotions SetGreyControl (CtlZoomHorizontal, (IsWazoo (GetMyWindowWindowPtr (Win)) && !IsLonelyWazoo (GetMyWindowWindowPtr (Win))) || !win->isActive); // restore clip region InfiniteClip (winPort); // redraw InvalContent (win); } /************************************************************************ * ABClose - close the window ************************************************************************/ Boolean ABClose (MyWindowPtr win) { NickStructHandle theData; NickStructPtr pData; short ab, nick, numABs, numNicks; Boolean discardChanges; discardChanges = false; if (AB.inited) { // Check to see if there's stuff we need to save if (IsDirtyWindow (win)) { // Engage in conversation with the user... switch (WannaSave (win)) { case WANNA_SAVE_CANCEL: case CANCEL_ITEM: return (false);; break; case WANNA_SAVE_DISCARD: discardChanges = true; break; case WANNA_SAVE_SAVE: if (!ABSave ()) return (false); break; } } // Old crap, modified some... Play with the deletion and dirty flags numABs = NAliases; for (ab = 0; ab < numABs; ab++) { if (theData = (*Aliases)[ab].theData) { numNicks = GetHandleSize_ (theData) / sizeof (NickStruct); for (nick = 0, pData = *theData; nick < numNicks; nick++, pData++) if (pData->addressesDirty) { pData->addressesDirty = false; pData->notesDirty = false; pData->pornography = false; if (pData->deleted && discardChanges) pData->deleted = false; } } // Get rid of the sort data for each address book ZapHandle ((*Aliases)[ab].sortData); } // Need to restore the recipient list if we're discarding changes VanquishRecipientList (discardChanges); // Save the list of expanded address books SaveExpandedAddressBookNames (); // Get rid of in-memory nickname info if we're discarding changes if (discardChanges) { ZapAliases (); RegenerateAllAliases (false); } // Dispose of list LVDispose (&NickList); // Dispose of any label fields DisposeLabelField (NickField); // Vanquish the tab control to memory hell DisposeTabControl (Tabs, false); AB.win = nil; AB.inited = false; } return (true); } /************************************************************************ * ABUpdate - draw the window ************************************************************************/ void ABUpdate (MyWindowPtr win) { CGrafPtr winPort = GetMyWindowCGrafPtr (win); // list DrawThemeListBoxFrame (&NickList.bounds, kThemeStateActive); LVDraw (&NickList,MyGetPortVisibleRegion(winPort), false, false); // vertical divider if (!Collapsed) DrawDivider (&VertDivider, true); } /************************************************************************ * ABActivate - activate the window ************************************************************************/ void ABActivate (MyWindowPtr win) { short ab, nick; // save the info on the tab whenever the address book goes inactive (which forces an update to the // current nickname if, for instance, we switch to a comp window and expect to do auto expand). if (!win->isActive) if (GetSelectedABNick (1, &ab, &nick)) ReplaceNicknameData (Tabs, ab, nick); LVActivate (&NickList, win->isActive); TabActivate (Tabs, win->isActive); ABSetControls (win->isActive); } /********************************************************************** * ABFind - find in the Address Book window * * ¥ First, check to see if the text appears in the current edit field * ¥ If so, select it and we're done * ¥ If not, search the rest of the fields for this nickname starting * with the next tab-able edit field. * ¥ When the text is found, select it. If it is not found.... * ¥ Step through the nickname list, testing each nickname to * see if the search text appears in either the nickname, addresses * or notes. * ¥ If a match is made, select that nickname in the list, then search * for the text as above. **********************************************************************/ Boolean ABFind (MyWindowPtr win, PStr what) { WindowPtr winWP = GetMyWindowWindowPtr (win); PETEHandle pte; PETEDocInfo info; NickFoundType found; long offset; found = false; UserSelectWindow (winWP); SetPort (GetWindowPort (winWP)); if (!win->isActive) ActivateMyWindow (winWP, true); // We're done if the text is found in the current PETE, so look for it! if (pte = win->pte) { PETEGetDocInfo (PETE, pte, &info); if ((offset = PeteFindString (what, info.selStop, pte)) >= 0) { PeteSelect (win, pte, offset, offset + *what); PeteScroll (pte, pseCenterSelection, pseCenterSelection); found = true; } } // darn! The text is not in the current field. Give the other fields associated with the selected nickname a chance if (!found) found = FindStringInSelectedNickname (what, false); // Okay, see if the search string is present in some other nickname if (!found) if (FindNextNicknameContainingString (win, what)) found = FindStringInSelectedNickname (what, true); return (found); } // // FindNextNicknameContainingString // // Find (and select) the next nickname containing the search string. NickFoundType FindNextNicknameContainingString (MyWindowPtr win, PStr what) { VLNodeInfo data; NickFoundType found; short ab, nick, endNick, abStop, nickStop, index; Boolean wrapped, done, caseSens; // Start searching from the current selection if (LVGetItem (&NickList, 1, &data, true)) { // Make sure the current tab information is saved GetABNick (data.nodeID, &ab, &nick); ReplaceNicknameData (Tabs, ab, nick); index = data.rowNum + 1; } else index = 1; GetIndexedABNick (index, &ab, &nick); abStop = ab; nickStop = nick; endNick = -1; // Means scan to the end of the address book wrapped = false; found = nickFoundNothing; done = false; caseSens = PrefIsSet(PREF_SENSITIVE); LSetDrawingMode (false, NickList.hList); while (!found && !done) { if (CommandPeriod || EjectBuckaroo) break; if (!IsPluginAddressBook (ab)) found = FindStringInAddressBook (ab, nick, &endNick, what, caseSens); if (!found) // Are we back where it all started? If so, we be done. if (wrapped && ab == abStop && (endNick == nickStop || endNick == -1)) done = true; else { // Advance to the next address book, maybe wrapping around back to the first one if (++ab == NAliases) { ab = 0; wrapped = true; } // Start at the first nickname and end... either with the last in the address book or the last after the wrap nick = -1; endNick = ab == abStop ? nickStop : -1; } } LSetDrawingMode (true, NickList.hList); // If we found something, display it (whoopee!), maybe expanding the address book if (found) { if ((*Aliases)[ab].collapsed) { LVGetItemWithNodeID (&NickList, MakeABNodeID (ab), &data); LVExpand (&NickList, MakeABNodeID (ab), data.name, true); } ABNickLVSelect (MakeNodeID (ab, endNick), false); } return (found); } // // FindStringInAddressBook // // Fings a search string in an address book, starting from a particular nickname, // and returning the found nickname in the end index. // // When -1 is passed for 'startNick' it means to scan the whole address book. // NickFoundType FindStringInAddressBook (short ab, short startNick, short *endNick, PStr what, Boolean caseSens) { NickFoundType found; short *sort, count; Boolean foundStartNick; found = nickFoundNothing; foundStartNick = startNick == -1; SortAddressBook (ab); if ((*Aliases)[ab].sortData && (*Aliases)[ab].theData) { count = GetHandleSize ((*Aliases)[ab].sortData) / sizeof (short); sort = LDRef ((*Aliases)[ab].sortData); while (count-- && !found && *sort != *endNick) { CycleBalls(); if (CommandPeriod || EjectBuckaroo) break; if (*sort == startNick) foundStartNick = true; if (foundStartNick && !(*((*Aliases)[ab].theData))[*sort].deleted) if (found = FindStringInNickName (ab, *sort, what, caseSens)) *endNick = *sort; ++sort; } UL ((*Aliases)[ab].sortData); } return (found); } NickFoundType FindStringInNickName (short ab, short nick, PStr what, Boolean caseSens) { AttributeValueHandle avPairs; AttributeValuePtr avPairPtr; NickFoundType found; Str255 fieldValue; Handle notes, addresses, leftovers; short count, i; found = nickFoundNothing; avPairs = nil; leftovers = nil; GetNicknameNamePStr (ab, nick, fieldValue); // See if the search string is in the name if (SearchPtrPtr (what + 1, *what, fieldValue + 1, 0, *fieldValue, caseSens, false, nil) >=0) found = nickFoundNickname; else // Didn't match on name, continue checking notes if (notes = GetNicknameData (ab, nick, false, true)) if (SearchPtrHandle (what + 1, *what, notes, 0, caseSens, false, nil) >=0) if (avPairs = ParseAllAttributeValuePairs (notes, &leftovers, avAllSearchablePairs, avPairUnknown)) { avPairPtr = LDRef (avPairs); LDRef (notes); count = GetHandleSize (avPairs) / sizeof (AttributeValueRec); for (i = 0; i < count && !found; ++i, ++avPairPtr) if (SearchPtrPtr (what + 1, *what, *notes + avPairPtr->valueOffset, 0, avPairPtr->valueLength, caseSens, false, nil) >=0) found = nickFoundNotes; // Found it in an attribute value pair (that is not hidden) -- so we can select this text in the proper tagged field UL (notes); } // Maybe the text is in the leftovers if (!found && leftovers) if (SearchPtrHandle (what + 1, *what, leftovers, 0, caseSens, false, nil) >=0) found = nickFoundNotes; // See if the search string is present in the addresses if (!found) if (addresses = GetNicknameData (ab, nick, true, true)) if (SearchPtrHandle (what + 1, *what, addresses, 0, caseSens, false, nil) >=0) found = nickFoundAddresses; ZapHandle (avPairs); ZapHandle (leftovers); return (found); } // // FindStringInSelectedNickname // // Find the next occurence of the search sting in the selected nickname. // Boolean FindStringInSelectedNickname (PStr what, Boolean checkNickField) { PETEHandle pte; long offset; short ab, nick, oldTabIndex; Boolean found; found = false; if (GetSelectedABNick (1, &ab, &nick)) { if (checkNickField) { // Remove the current focus ABClearKeyboardFocus (); if (pte = GetLabelFieldPete (NickField)) { if ((offset = PeteFindString (what, 0, pte)) >= 0) { // Switch to the first tab oldTabIndex = GetActiveTabIndex (Tabs); SetControlValue (Tabs, 1); CurrentTab = SwitchTabs (Tabs, oldTabIndex, GetActiveTabIndex (Tabs), PREF_LAST_NICK_TAB); // Move the focus to the nickname field ABSetKeyboardFocus (focusNickField); // Select the search string in the field PeteSelect (Win, pte, offset, offset + *what); PeteScroll (pte, pseCenterSelection, pseCenterSelection); found = true; } } } // Look for it in the tab if (!found) if (found = TabFindString (Tabs, NickFocus == focusTabControl, what)) NickFocus = focusTabControl; } return (found); } #if 0 /********************************************************************** * ABFindInCollapsed - search a collapsed folder **********************************************************************/ Boolean ABFindInCollapsed (MyWindowPtr win, ViewListPtr pView, PStr what, VLNodeID nodeID) { VLNodeInfo data; CGrafPtr winPort; Str31 name; short totalNicks, nick, ab; ab = GetAddressBook (nodeID); totalNicks = (GetHandleSize_ ((*Aliases)[ab].theData) / sizeof (NickStruct)); for (nick = 0; nick < totalNicks; nick++) if (FindStrStr (what, GetNicknameNamePStr (ab, nick, name)) >= 0) { LVGetItemWithNodeID (&NickList, MakeABNodeID (ab), &data); LVExpand (&NickList, MakeABNodeID (ab), data.name, true); LVSelect (pView, MakeNodeID (ab, nick), name, false); winPort = GetMyWindowCGrafPtr (win); LVDraw(pView,winPort->visRgn,true,false); return true; } return false; } #endif Boolean ABDirty (MyWindowPtr win) { return (win->isDirty || AnyNicknamesDirty (kAddressBookUndefined) || (Tabs && IsTabDirty (Tabs))); } Boolean ABHasSelection (MyWindowPtr win) { UHandle text; long start, stop; if (LVCountSelection (&NickList)) return (true); if (ValidNickname (AB.nickDisplayed) && TabHasSelection (Tabs)) return (true); if (win->pte && !PeteGetTextAndSelection (win->pte, &text, &start, &stop)) return (stop!=start); return (false); } void ABMenuEnable (MyWindowPtr win) { if (NickFocus == focusTabControl && ValidNickname (AB.nickDisplayed)) TabMenuEnable (Tabs, (*Aliases)[AB.abDisplayed].ro); } void ABSetKeyboardFocus (NickFocusType nickFocus) { // If the focus is not changing, why bother, ya know? if (nickFocus == NickFocus) return; // First, clear the old focus if (!ABClearKeyboardFocus ()) return; // Next, set the new focus switch (NickFocus = nickFocus) { case focusNickList: LVSetKeyboardFocus (&NickList, true); break; case focusNickField: SetKeyboardFocus (GetMyWindowWindowPtr (Win), NickField, kControlEditTextPart); PeteFocus(Win,GetLabelFieldPete(NickField),true); break; case focusTabControl: TabSetKeyboardFocus (Tabs, focusTabControlDirect, true); break; case focusNickTabAdvance: TabSetKeyboardFocus (Tabs, focusTabControlAdvance, true); NickFocus = focusTabControl; break; case focusNickTabReverse: TabSetKeyboardFocus (Tabs, focusTabControlReverse, true); NickFocus = focusTabControl; break; } } Boolean ABClearKeyboardFocus (void) { switch (NickFocus) { case focusNickList: LVSetKeyboardFocus (&NickList, false); break; case focusNickField: if (!ABMaybeRenameNickname (Win)) return (false); SetKeyboardFocus (GetMyWindowWindowPtr (Win), NickField, kControlFocusNoPart); PeteFocus(Win,GetLabelFieldPete(NickField),false); break; case focusTabControl: TabSetKeyboardFocus (Tabs, focusTabControlDirect, false); break; } NickFocus = focusNoChange; if (Win->pte) PeteFocus(Win,Win->pte,false); return (true); } void ABAdvanceKeyboardFocus (void) { NickFocusType newFocus; // If the window is collapsed, there's nowhere for the focus to go if (Collapsed) return; newFocus = focusNoChange; switch (NickFocus) { case focusNickList: // Advance the focus within the nickname list, if we don't want the focus anymore, give it to the nickname field if (ValidNickname (AB.nickDisplayed)) if (LVAdvanceKeyboardFocus (&NickList)) newFocus = focusNickField; break; case focusNickField: // Advance the focus to the Tab control newFocus = focusNickTabAdvance; break; case focusTabControl: // Advance the focus within the Tab control, if we don't want the focus anymore, give it to the nickname list if (TabAdvanceKeyboardFocus (Tabs)) newFocus = focusNickList; break; } // If the focus is changing, see if the new item is willing to accept the focus, if not we advance again if (newFocus != focusNoChange) ABSetKeyboardFocus (newFocus); } void ABReverseKeyboardFocus (void) { NickFocusType newFocus; newFocus = focusNoChange; switch (NickFocus) { case focusNickList: // Reverse the focus within the nickname list, if we don't want the focus anymore, give it to the Tab control if (ValidNickname (AB.nickDisplayed)) if (LVReverseKeyboardFocus (&NickList)) newFocus = focusNickTabReverse; break; case focusNickField: // Reverse the focus to the nickname list newFocus = focusNickList; break; case focusTabControl: // Reverse the focus within the Tab control, if we don't want the focus anymore, give it to the nickname field if (TabReverseKeyboardFocus (Tabs)) newFocus = focusNickField; break; } // If the focus is changing, see if the new item is willing to accept the focus, if not we reverse again if (newFocus != focusNoChange) ABSetKeyboardFocus (newFocus); } /************************************************************************ * ABKey - key stroke ************************************************************************/ Boolean ABKey (MyWindowPtr win, EventRecord *event) { short key = (event->message & 0xff), newTabIndex; Boolean result; result = false; // Where is the focus? switch (NickFocus) { case focusNickList: result = LVKey (&NickList, event); break; case focusTabControl: result = TabKey (Tabs, event); break; } if (!result || key == tabChar) switch (key) { case tabChar: if (!Collapsed) { if (event->modifiers & optionKey) { newTabIndex = GetActiveTabIndex (Tabs) + (event->modifiers & shiftKey ? -1 : 1); newTabIndex = newTabIndex ? (newTabIndex > GetTabPaneCount (Tabs) ? 1 : newTabIndex) : (GetTabPaneCount (Tabs)); SetControlValue (Tabs, newTabIndex); // Pretend we clicked on this tab to do all the right switching of fields ABHit (win, event, abTabs, 0); } else { if (event->modifiers & shiftKey) ABReverseKeyboardFocus (); else ABAdvanceKeyboardFocus (); if (win->pte) PeteSelectAll (win, win->pte); } result = true; } break; default: if (!(event->modifiers & cmdKey)) { if (DirtyKey (key) && ValidAddressBook (AB.abDisplayed) && (*Aliases)[AB.abDisplayed].ro) AlertStr (READ_ONLY_ALRT, Stop, nil); else { event->what = keyDown; (void) PeteEdit (win, win->pte, peeEvent, (void*) event); // (jp) For nickname completion } result = true; } } return (result); } /************************************************************************ * ABClick - click in nickname window ************************************************************************/ void ABClick (MyWindowPtr win, EventRecord *event) { NickFocusType oldNickFocus; WindowPtr WinWP = GetMyWindowWindowPtr (Win); Point pt; short part, i; Rect tabRect; SetPort (GetMyWindowCGrafPtr (win)); pt = event->where; GlobalToLocal (&pt); // First, check to see if the click was in the list while the PETE was not. When this is the // case we need to remove the focus before handing the click to the list view. We'll also check // to see if we should rename the current nickname (as the ListView is kinda picky about the // selection and sort states). if (PtInRect (pt, &NickList.bounds)) { if (!ABMaybeRenameNickname (win)) return; ABSetKeyboardFocus (focusNickList); } // Now, let's give the list view a shot at the click if (!LVClick (&NickList, event)) { if (!win->isActive) { SelectWindow_ (WinWP); UpdateMyWindow (WinWP); // Have to update manually since no events are processed } // tab content (since the toolbox is not nice enough to tell about clicks in the content... sheesh) GetTabContentRect (Tabs, &tabRect); if (PtInRect (pt, &tabRect) && ValidNickname (AB.nickDisplayed)) { oldNickFocus = NickFocus; if (NickFocus != focusTabControl) if (!ABClearKeyboardFocus ()) return; if (ClickTab (Tabs, event, pt)) NickFocus = focusTabControl; else ABSetKeyboardFocus (oldNickFocus); } else { // Let's look at the controls we know about for (i = 0; i < abControlCount; i++) if (part = TestControl (Controls[i], pt)) { // For now we're going to intercept mouse downs in label fields and pass them down to our hit routine. // Eventually it would be cool (and maybe easy) to have the label field user pane respond to tracking messages // We also have to qluify the part test against 'kControlEditTextPart' to not be a hit in a tab control // because the tab control returns the index og the hit tab in the part... and since we have 5 tabs, clicks // in the Notes field were being swallowed by the part==kControlEditTextPart test. if (!ControlIsGrey (Controls[i])) if ((part == kControlEditTextPart && Controls[i] != Tabs) || TrackControl (Controls[i], pt, (void *)(-1))) { ABHit (win, event, i, part); AuditHit ((event->modifiers&shiftKey)!=0, (event->modifiers&controlKey)!=0, (event->modifiers&optionKey)!=0, (event->modifiers&cmdKey)!=0, false, GetWindowKind(WinWP), AUDITCONTROLID(GetWindowKind(WinWP),i), event->what); break; } } // If we did not hit a control if (i == abControlCount) { // The vertical divider, by chance? if (!Collapsed && PtInSloppyRect (pt, &VertDivider, 1)) NickWDragVDivider (pt); } } } ABSetControls (win->isActive); } /************************************************************************ * ABCursor - set the cursor properly for the address book window ************************************************************************/ void ABCursor (Point mouse) { int cursor; if (!PeteCursorList (Win->pteList, mouse)) { cursor = arrowCursor; if (PtInSlopRect (mouse, VertDivider, 1)) cursor = DIVIDER_CURS; SetMyCursor (cursor); } } /************************************************************************ * ABSetControls - Set the controls based on the state of the selected * information in the list. ************************************************************************/ void ABSetControls (Boolean isActive) { short ab, nick, count; Boolean canAddNickname, canAddAddressBook, canRemove, anyNicksSelected, singleNickSelected, greyTab; // Here's a neato oddity of the toolbox... the active state of a control becomes // hosed if your activate or deactivate a control while the mouse button is down... // even though the mouse might not be down IN that particulat control. Who knew? if (StillDown ()) return; count = ABDetermineSelectionConditions (&canAddNickname, &canAddAddressBook, &canRemove, &anyNicksSelected, &singleNickSelected); SetGreyControl (CtlNewNickname, !canAddNickname || !isActive); SetGreyControl (CtlNewAddressBook, !canAddAddressBook || !isActive); SetGreyControl (CtlRemove, !canRemove || !isActive); SetGreyControl (CtlTo, !anyNicksSelected || !isActive); SetGreyControl (CtlCc, !anyNicksSelected || !isActive); SetGreyControl (CtlBcc, !anyNicksSelected || !isActive); SetGreyControl (CtlChat, !singleNickSelected || !isActive || !ABIsChattable()); SetGreyControl (CtlRecipient, !anyNicksSelected || !isActive); SetGreyControl (CtlZoomHorizontal, !isActive); if (IsWazoo (GetMyWindowWindowPtr (Win)) && !IsLonelyWazoo (GetMyWindowWindowPtr (Win))) HideControl(CtlZoomHorizontal); else ShowControl(CtlZoomHorizontal); // nickname field and tabs if (count == 1) GetSelectedABNick (1, &ab, &nick); else { ab = kAddressBookUndefined; nick = kNickUndefined; } SetGreyControl (CtlDoNotSync, !anyNicksSelected || (ab != kAddressBookUndefined && (*Aliases)[ab].ro) || !isActive ); SetGreyControl (NickField, !singleNickSelected || (ab != kAddressBookUndefined && (*Aliases)[ab].ro) || !isActive); ABSetRecipientButton (); ABSetSyncButton (); greyTab = !singleNickSelected || (ab != kAddressBookUndefined && (*Aliases)[ab].ro) || !isActive; IterateTabPanes (Tabs, SetGreyTabProc, &greyTab); Win->hasSelection = anyNicksSelected || MyWinHasSelection (Win); } // // ABDetermineSelectionConditions // // Takes a peek at various nickname selection criteria so that we can know which buttons need to be // deactivated -- and maybe why. // // Returns the number of items currently selected. // short ABDetermineSelectionConditions (Boolean *canAddNickname, Boolean *canAddAddressBook, Boolean *canRemove, Boolean *anyNicksSelected, Boolean *singleNickSelected) { short count, ab, nick, i; *canAddNickname = true; *canAddAddressBook = HasFeature (featureMultipleNicknameFiles); *canRemove = LVCountSelection (&NickList) ? true : false; *anyNicksSelected = false; *singleNickSelected = false; count = LVCountSelection (&NickList); // Take a trip through all the selections looking for relevant conditions for (i = 1; i <= count; ++i) if (GetSelectedABNick (i, &ab, &nick)) if (ab >= 0) { if ((*Aliases)[ab].ro) { *canRemove = false; *canAddNickname = false; } if (nick == kNickUndefined) { #ifdef VCARD if (IsEudoraAddressBook (ab) || IsHistoryAddressBook (ab) || IsPersonalAddressBook (ab)) #else if (IsEudoraAddressBook (ab) || IsHistoryAddressBook (ab)) #endif *canRemove = false; } else { *anyNicksSelected = true; if (count == 1) *singleNickSelected = true; } } return (count); } Boolean SetGreyTabProc (ControlHandle tabControl, ControlHandle tabPane, short tabIndex, Boolean *shdBeGrey) { SetGreyControl (tabPane, *shdBeGrey); return (false); } void ABSelectNickname (VLNodeID nodeID) { // On a selection during a drag, set the nick selection to be nothing if (LVDragSelectInProgress (&NickList)) nodeID = MakeNodeID (kAddressBookUndefined, kNickUndefined); if (ABDisplayNicknameInTab (GetAddressBook (nodeID), GetNickname (nodeID))) ABSetControls (Win->isActive); } void ABUnselectNickname (VLNodeID nodeID) { short ab, nick, count; ab = GetAddressBook (nodeID); nick = GetNickname (nodeID); // If we are unselecting the _displayed_ nickname, replace its data ReplaceNicknameData (Tabs, ab, nick); // When individual nicknames are unselected, check to see if any are selected at all. // If not, we'll deactivate controls. (Note: we might want to instead use this: count = LVCountSelection (&NickList); if (!count) { if (ABDisplayNicknameInTab (kAddressBookUndefined, kNickUndefined)) ABSetControls (false); } else // If by unselecting a name we now have exactly one name selected.... display it if (count == 1) { GetSelectedABNick (1, &ab, &nick); if (ABDisplayNicknameInTab (ab, nick)) ABSetControls (true); } } void ABSetRecipientButton (void) { short ab, nick, count, value, i; Boolean isRecipient; value = -1; // We're going to loop through the selected list items to see whether or not we have a // mix of recipient list settings (so that we can set the control to its "mixed" setting). count = LVCountSelection (&NickList); for (i = 1; i <= count && value != kControlCheckBoxMixedValue; ++i) { GetSelectedABNick (i, &ab, &nick); if (nick != kNickUndefined) { isRecipient = IsNicknameOnRecipList (ab, nick); if (value == -1) value = isRecipient ? kControlCheckBoxCheckedValue : kControlCheckBoxUncheckedValue; else if ((value == kControlCheckBoxCheckedValue && !isRecipient) || (value == kControlCheckBoxUncheckedValue && isRecipient)) value = kControlCheckBoxMixedValue; } } SetControlValue (CtlRecipient, value); } void ABSetSyncButton (void) { short ab, nick, count, value, i; Boolean isPrivate; value = -1; // We're going to loop through the selected list items to see whether or not we have a // mix of recipient list settings (so that we can set the control to its "mixed" setting). count = LVCountSelection (&NickList); for (i = 1; i <= count && value != kControlCheckBoxMixedValue; ++i) { GetSelectedABNick (i, &ab, &nick); if (nick != kNickUndefined) { isPrivate = IsNicknamePrivate (ab, nick); if (value == -1) value = isPrivate ? kControlCheckBoxCheckedValue : kControlCheckBoxUncheckedValue; else if ((value == kControlCheckBoxCheckedValue && !isPrivate) || (value == kControlCheckBoxUncheckedValue && isPrivate)) value = kControlCheckBoxMixedValue; } } SetControlValue (CtlDoNotSync, value); } /************************************************************************ * ABHit - a control item was hit ************************************************************************/ void ABHit (MyWindowPtr win, EventRecord *event, short whichControl, short part) { // Certain control hits should be preflighted with a possible nickname field save if (whichControl == abViewByPopup || whichControl == abNewNicknameButton || whichControl == abNewAddressBookButton || whichControl == abToButton || whichControl == abCcButton || whichControl == abBccButton || whichControl == abChatButton || whichControl == abRecipientCheckbox) if (!ABMaybeRenameNickname (win)) return; switch (whichControl) { case abViewByPopup: DoViewByMenu (); break; case abNewNicknameButton: DoNewNickname (event->modifiers); break; case abNewAddressBookButton: DoNewAddressBook (); break; case abRemoveButton: ABRemove (); break; case abToButton: ABMessageTo (TO_HEAD, event->modifiers); break; case abCcButton: ABMessageTo (CC_HEAD, event->modifiers); break; case abChatButton: ABChat (event->modifiers); break; case abBccButton: ABMessageTo (BCC_HEAD, event->modifiers); break; case abZoomHorizontalButton: DoHorizontalZoom (win); break; case abRecipientCheckbox: DoRecipientList (CtlRecipient); break; case abDoNotSyncCheckbox: DoDoNotSync (CtlDoNotSync); break; case abNickField: if (part == kControlEditTextPart) { ABSetKeyboardFocus (focusNickField); PeteEdit (win, GetLabelFieldPete (NickField), peeEvent, (void*) event); } break; case abTabs: // Remove the focus from the current tab... if (NickFocus == focusTabControl) TabSetKeyboardFocus (Tabs, focusTabControlDirect, false); CurrentTab = SwitchTabs (Tabs, CurrentTab, GetActiveTabIndex (Tabs), PREF_LAST_NICK_TAB); /// ...and put it back onto the first field of the new tab if (NickFocus == focusTabControl) TabSetKeyboardFocus (Tabs, focusTabControlDirect, ValidNickname (AB.nickDisplayed)); break; } } void ABMessageTo (short strResID, long modifiers) { WindowPtr WinWP = GetMyWindowWindowPtr(Win); short ab, nick; MyWindowPtr insertedWin; // save the info on the tab whenever a recipient button has been clicked if (GetSelectedABNick (1, &ab, &nick)) ReplaceNicknameData (Tabs, ab, nick); insertedWin = InsertTheAlias (strResID, (modifiers & optionKey) != 0); if ((modifiers & (shiftKey | cmdKey))) { if (FrontWindow_ () != WinWP) UserSelectWindow (WinWP); } else if (FrontWindow_() == WinWP) { UserSelectWindow(GetMyWindowWindowPtr(insertedWin)); UpdateMyWindow (WinWP); } } /************************************************************************ * ABHelp - help for the alias window ************************************************************************/ void ABHelp (MyWindowPtr win,Point mouse) { NickHelpType help; Rect tipRect; short i, ab, nick; Boolean canAddNickname, canAddAddressBook, canRemove, anyNicksSelected, singleNickSelected; Rect cntlRect; ABDetermineSelectionConditions (&canAddNickname, &canAddAddressBook, &canRemove, &anyNicksSelected, &singleNickSelected); help = noNickHelp; if (PtInRect (mouse, &NickList.bounds)) { tipRect = NickList.bounds; help = nickHelpList; } else if (PtInSloppyRect (mouse, &VertDivider, 1)) { tipRect = VertDivider; help = nickHelpVertDivider; } else for (i = 0; help == noNickHelp && i < abControlCount; i++) if (PtInRect (mouse, GetControlBounds(Controls[i],&cntlRect))) { GetControlBounds(Controls[i],&tipRect); switch (i) { case abViewByPopup: help = nickHelpViewByPopup; break; case abNewNicknameButton: help = canAddAddressBook ? nickHelpNewNicknameButton : nickHelpNewNicknameButtonDimmed; break; case abNewAddressBookButton: help = nickHelpNewAddressBookButton; break; case abRemoveButton: if (canRemove) help = nickHelpRemoveButton; else { help = nickHelpRemoveButtonDimmed; if (GetSelectedABNick (i, &ab, &nick)) if (ab >= 0) { if ((*Aliases)[ab].ro) help = nickHelpRemoveButtonDimmedReadOnly; else if (nick == kNickUndefined) if (IsEudoraAddressBook (ab)) help = nickHelpRemoveButtonDimmedEudoraNicknames; else if (IsHistoryAddressBook (ab)) help = nickHelpRemoveButtonDimmedHistoryList; } } break; case abToButton: help = anyNicksSelected ? (TopCompositionWindow (true, true) ? nickHelpToButtonAddToTopmost : nickHelpToButton) : nickHelpToButtonDimmed; break; case abCcButton: help = anyNicksSelected ? (TopCompositionWindow (true, true) ? nickHelpCcButtonAddToTopmost : nickHelpCcButton) : nickHelpCcButtonDimmed; break; case abBccButton: help = anyNicksSelected ? (TopCompositionWindow (true, true) ? nickHelpBccButtonAddToTopmost : nickHelpBccButton) : nickHelpBccButtonDimmed; break; case abZoomHorizontalButton: help = Collapsed ? nickHelpZoomHorizontalButtonCollapsed : nickHelpZoomHorizontalButtonExpanded; break; case abRecipientCheckbox: help = anyNicksSelected ? nickHelpRecipientCheckbox : nickHelpRecipientCheckboxDimmed; break; case abNickField: help = anyNicksSelected ? (singleNickSelected ? nickHelpNickField : nickHelpNickFieldDimmedMultipleSelection) : nickHelpNickFieldDimmedNothingSelected; break; case abTabs: help = anyNicksSelected ? (singleNickSelected ? nickHelpTabs : nickHelpTabsDimmedMultipleSelection) : nickHelpTabsDimmedNothingSelected; break; } } if (help) MyBalloon (&tipRect, 100, 0, NICK_HELP_STRN + help, 0, nil); } /************************************************************************ * ABMenu - menu choice in the alias window ************************************************************************/ Boolean ABMenu (MyWindowPtr win, int menu, int item, short modifiers) { TabObjectPtr objectPtr; SendDragDataInfo sendDragData; Str255 tag, name; Handle copyText; OSErr theError; short ab, nick; ScrapRef scrap; if (!TabMenu (Tabs, menu, item, modifiers)) switch (menu) { case FILE_MENU: switch (item) { case FILE_SAVE_ITEM: if (IsDirtyWindow (win)) ABSave (); return (True); break; case FILE_SAVE_AS_ITEM: DoAddressBookExport ((modifiers&shiftKey)!=0); break; case FILE_PRINT_ITEM: case FILE_PRINT_ONE_ITEM: PrintAddressBookWindow((modifiers&shiftKey)!=0, item==FILE_PRINT_ONE_ITEM); break; } break; case EDIT_MENU: if (win->pte) { PETEHandle pteEmail = nil, pteOtherEmail = nil; DoIndependentMenu (menu, item, modifiers); win->hasSelection = MyWinHasSelection (win); if (objectPtr = FindObjectWithTag (Tabs, GetRString (tag, ABReservedTagsStrn + abTagEmail))) { pteEmail = GetLabelFieldPete (objectPtr->control); UnlockObject (objectPtr); } if (objectPtr = FindObjectWithTag (Tabs, GetRString (tag, ABReservedTagsStrn + abTagOtherEmail))) { pteOtherEmail = GetLabelFieldPete (objectPtr->control); UnlockObject (objectPtr); } if ((item == EDIT_COPY_ITEM || item == EDIT_CUT_ITEM) && (win->pte == pteEmail || win->pte == pteOtherEmail)) ReformatClip(); return (true); } else switch (item) { case EDIT_SELECT_ITEM: if (NickFocus == focusNickList) LVSelectAll (&NickList); break; case EDIT_CLEAR_ITEM: if (NickFocus == focusNickList) ABRemove(); break; case EDIT_COPY_ITEM: Zero (sendDragData); sendDragData.flavor = A822_FLAVOR; theError = BuildDragData (&sendDragData, ©Text); if (!theError) { ClearCurrentScrap(); GetCurrentScrap(&scrap); theError = PutScrapFlavor(scrap,'TEXT',kScrapFlavorMaskNone,GetHandleSize (copyText),LDRef (copyText)); } ZapHandle (copyText); break; } break; case SPECIAL_MENU: switch (AdjustSpecialMenuSelection(item)) { case SPECIAL_MAKE_NICK_ITEM: if (modifiers&shiftKey) MakeNickFromSelection(win); else MakeNicknameFromSelectedNicknames (); break; } break; case WINDOW_MENU: if (item == WIN_PH_ITEM && (modifiers & shiftKey) && LVCountSelection (&NickList) == 1) { if (GetSelectedABNick (1, &ab, &nick)) { GetTaggedFieldValueStr (ab, nick, GetRString (tag, ABReservedTagsStrn + abTagName), name); if (*name) { OpenPh (name); return (true); } } } break; } return (false); } Boolean ABSave (void) { short ab, nick; Boolean bornAgain; // Does the nickname field need to be saved before we save? if (!ABMaybeRenameNickname (Win)) return (false); // Make sure the current tab information is saved if (GetSelectedABNick (1, &ab, &nick)) ReplaceNicknameData (Tabs, ab, nick); if (bornAgain = SaveAliases (true)) ABClean (); return (bornAgain); } void ABTickle (void) { short abDisplayed, nickDisplayed; if (AB.inited) { abDisplayed = AB.abDisplayed; nickDisplayed = AB.nickDisplayed; InvalidListView (&NickList); // Reselect the previous items if (!(abDisplayed == kAddressBookUndefined && nickDisplayed == kNickUndefined)) ABNickLVSelect (MakeNodeID (abDisplayed, nickDisplayed), true); TickleMe = false; } } void ABTickleHardEnoughToMakeYouPuke(void) { if (AliasWinIsOpen()) { short which; for (which=0;whichcontrol); if (win->pte == pte) IdleControls (GetMyWindowWindowPtr (win)); UnlockObject (objectPtr); } if (NickFocus==focusNickField) { Boolean singleNickSelected,ratSass; ABDetermineSelectionConditions(&ratSass, &ratSass, &ratSass, &ratSass, &singleNickSelected); if (!singleNickSelected) ABClearKeyboardFocus(); } } } /********************************************************************** * ABDragHandler - handle drags * * Can do internal dragging of nicknames or external dragging and dropping * of messages **********************************************************************/ OSErr ABDragHandler (MyWindowPtr win, DragTrackingMessage which, DragReference drag) { VLNodeInfo targetInfo; OSErr theError; Point initialMouse, currentMouse; short ab, nick; #ifdef VCARD short modifiers, mouseDownModifiers, mouseUpModifiers; Boolean isVCardDrag, isSpecDrag; #endif Boolean isListItemDrag, isDragInList, is822Drag, isTextDrag, isGraphicFileDrag, isNickDrag, isHFSDrag, isTabDrag; theError = dragNotAcceptedErr; is822Drag = DragIsInteresting (drag, A822_FLAVOR, nil); isTextDrag = DragIsInteresting (drag, 'TEXT', nil); isNickDrag = DragIsInteresting (drag, kNickDragType, nil); isHFSDrag = DragIsInteresting (drag, flavorTypeHFS, nil); #ifdef VCARD isSpecDrag = DragIsInteresting (drag, SPEC_FLAVOR, nil) && IsVCardAvailable (); #endif isTabDrag = TabDragIsInteresting (Tabs, drag); isListItemDrag = false; // Only check the drag origin if the drag started with this window if (DragSourceKind == GetWindowKind(GetMyWindowWindowPtr(win))) if (!GetDragOrigin (drag, &initialMouse)) { GlobalToLocal (&initialMouse); isListItemDrag = PtInRect (initialMouse, &NickList.bounds); } isDragInList = false; if (!GetDragMouse (drag, ¤tMouse, nil)) { GlobalToLocal (¤tMouse); isDragInList = PtInRect (currentMouse, &NickList.bounds); } if (isTabDrag = (isTabDrag && LVCountSelection (&NickList) == 1)) { GetSelectedABNick (1, &ab, &nick); isTabDrag = ValidNickname (nick); } // Calling IsGraphicFile is ssssssssssllllllloooooooowwwwwww. // We could speed it up with a global. isGraphicFileDrag = false; #ifdef VCARD isVCardDrag = false; #endif if (isHFSDrag) { FSSpec spec; HFSFlavor **dragData; dragData = nil; if (!MyGetDragItemData (drag, 1, flavorTypeHFS, &dragData)) { spec = (*dragData)->fileSpec; isGraphicFileDrag = IsGraphicFile (&spec); #ifdef VCARD if (IsVCardAvailable ()) isVCardDrag = IsVCardFile (&spec); #endif } ZapHandle (dragData); } #ifdef VCARD if (!is822Drag && !isTextDrag && !isNickDrag && !isHFSDrag && !isListItemDrag && !isTabDrag && !isSpecDrag) return (dragNotAcceptedErr); // Nothing here we want #else if (!is822Drag && !isTextDrag && !isNickDrag && !isHFSDrag && !isListItemDrag && !isTabDrag) return (dragNotAcceptedErr); // Nothing here we want #endif // If we're dragging TEXT that is not also a drag that originated in the list itself, check // to see if there is exactly one nickname selected in the list. If so, the addres book can // accept the drag and we return 'dragNotAcceptedErr' (yes, that's right... NOT accepted) so // that PETE handles the drag. If there is no list selection, or there are multiple selections // then the nickname fields are disabled and we return 'noErr' to make PETE think we were smart // enough to handle the drag ourselves. if (isTextDrag && !isListItemDrag && !isDragInList) { if (LVCountSelection (&NickList) == 1) { GetSelectedABNick (1, &ab, &nick); if (ValidNickname (nick)) return (dragNotAcceptedErr); } return (noErr); } #ifdef VCARD // If we are dragging a SPEC_FLAVOR that originated with ourselves, we're dragging a personal nickname // as a vCard. We'll allow this ONLY if a copy is taking place. GetDragModifiers (drag, &modifiers, &mouseDownModifiers, &mouseUpModifiers); if (isSpecDrag && isListItemDrag && !((modifiers|mouseDownModifiers|mouseUpModifiers)&optionKey)) return (dragNotAcceptedErr); #endif switch (which) { case kDragTrackingEnterWindow: case kDragTrackingLeaveWindow: case kDragTrackingInWindow: if (isNickDrag || (isHFSDrag && !isGraphicFileDrag) || (isListItemDrag && !isTextDrag)) theError = LVDrag (&NickList, which, drag); else if (isTabDrag) theError = TabDrag (Tabs, which, drag); break; case 0xfff: // Plop if (isTabDrag) theError = TabDrag (Tabs, which, drag); else if (isListItemDrag) theError = LVDrag (&NickList, which, drag); else if (LVDrop (&NickList, &targetInfo)) { #ifdef VCARD if (isHFSDrag && IsVCardAvailable ()) theError = ABFileDrop (drag, &targetInfo); else #endif theError = ABNickDrop (drag, &targetInfo); } else theError = noErr; // Dropped, just not very interesting break; } return (theError); } OSErr ABAddDragFlavors (long data) { SendDragDataInfo *pSendData; OSErr theError; #ifdef VCARD PromiseHFSFlavor promise; short count, index, ab, nick; Boolean anyNonVCards; #endif theError = noErr; pSendData = (SendDragDataInfo *) data; #ifdef VCARD // We need to see if the selected items exclusively consist of those in the // Personal Nicknames address book. If so, we will promise an HFS in the // drag and eventually deliver a vCard. If not, we treat the Personal Nickname // like any other nickname. We do this because we DON'T want to deliver // regular old nicknames as vCards (because these aren't OUR vCards). if (IsVCardAvailable ()) { anyNonVCards = false; count = LVCountSelection (&NickList); for (index = 1; index <= count && !anyNonVCards; ++index) if (GetSelectedABNick (index, &ab, &nick)) if (!IsPersonalAddressBook (ab) || !ValidNickname (nick)) anyNonVCards = true; if (!anyNonVCards) { Zero (promise); promise.fileType = VCARD_TYPE; promise.fileCreator = CREATOR; promise.promisedFlavor = SPEC_FLAVOR; theError = AddDragItemFlavor (pSendData->drag, 1L, flavorTypePromiseHFS, &promise, sizeof(promise), flavorNotSaved); if (!theError) theError = AddDragItemFlavor (pSendData->drag, 1L, SPEC_FLAVOR, nil, 0L, flavorNotSaved); UseFeature (featureVCard); } } #endif if (!theError) theError = AddDragItemFlavor (pSendData->drag, 1L, 'TEXT', nil, 0L, 0); if (!theError) theError = AddDragItemFlavor (pSendData->drag, 1L, kNickDragType, nil, 0L, 0); if (!theError) theError = AddDragItemFlavor (pSendData->drag, 1L, A822_FLAVOR, nil, 0L, 0); return (theError); } // // ABDoSendDragData // // Build a data handle from the selected nicknames. Based somewhat on the old NickDragToData // but using the new list view schtuff. // OSErr ABDoSendDragData (long data) { SendDragDataInfo *pSendData; Handle dragData; OSErr theError; Size dataSize; short ab, nick; // Save the current nickname before committing its data to a drag if (GetSelectedABNick (1, &ab, &nick)) ReplaceNicknameData (Tabs, ab, nick); pSendData = (SendDragDataInfo *) data; dragData = nil; theError = BuildDragData (pSendData, &dragData); if (!theError) if (dataSize = GetHandleSize (dragData)) { theError = SetDragItemFlavorData (pSendData->drag, pSendData->itemRef, pSendData->flavor, LDRef (dragData), dataSize, 0L); UL (dragData); } else theError = dragNotAcceptedErr; ZapHandle (dragData); return (theError); } void ABMove (VLNodeInfo *pToInfo, Boolean copy) { VLNodeID **movedList, *pItem; short selectCount, ab, nick, lastMovedAB, i; Boolean anyChanges; // well? // Save the current nickname before committing its data to a move if (GetSelectedABNick (1, &ab, &nick)) ReplaceNicknameData (Tabs, ab, nick); anyChanges = false; selectCount = LVCountSelection (&NickList); lastMovedAB = kAddressBookUndefined; movedList = (VLNodeID **) NuHandle (0); for (i = 1; i <= selectCount; i++) { if (GetSelectedABNick (i, &ab, &nick)) { // Is it just a nickname, or is it an entire address book? if (lastMovedAB != ab && ValidNickname (nick)) { if (DropNicknameOntoAddressBook (movedList, pToInfo, ab, nick, copy)) anyChanges = true; } else if (DropAddressBook (movedList, pToInfo, ab, copy)) { lastMovedAB = ab; anyChanges = true; } } } // Build a new list -- and mark any of the moved or copied items as selected if (anyChanges) { InvalidListView (&NickList); selectCount = GetHandleSize (movedList) / sizeof (VLNodeID); for (i = 0, pItem = *movedList; i < selectCount; ++i, ++pItem) ABNickLVSelect (*pItem, true); } } // // ABNickDrop // // Handle drops in the nickname list // OSErr ABNickDrop (DragReference drag, VLNodeInfo *targetInfo) { BinAddrHandle mungedAddresses; Str31 name; OSErr theError; Handle dragData, addresses, notes; long offset, dragDataSize; short count, item, newNick, ab, nick; theError = noErr; count = MyCountDragItems (drag); for (item = 1; !theError && item <= count; ++item) { dragData = nil; theError = MyGetDragItemData (drag, item, kNickDragType, &dragData); if (!theError) { offset = 0; dragDataSize = GetHandleSize (dragData); while (offset < dragDataSize && !theError) { mungedAddresses = nil; addresses = nil; notes = nil; theError = GetNickFlavorDragData (dragData, &offset, &ab, &nick, name, &addresses, ¬es); if (!theError) MaybeApplySplittingAlgorithm (notes); if (!theError) theError = SuckAddresses (&mungedAddresses, addresses, true, true, false, nil); if (!theError) if (!ValidNickname (newNick = AddNickname (mungedAddresses, notes, GetAddressBook (targetInfo->nodeID), name, false, nrDifferent, false))) theError = userCanceledErr; ZapHandle (mungedAddresses); ZapHandle (addresses); ZapHandle (notes); } } ZapHandle (dragData); } InvalidListView (&NickList); return (theError); } #ifdef VCARD void DecodeQuotedPrintable (Handle value); OSErr ABFileDrop (DragReference drag, VLNodeInfo *targetInfo) { NickFromVCardItemRec nickFromVCard; BinAddrHandle addresses; FSSpec spec; HFSFlavor **dragData; Handle vcardData; Str255 nickname, fullName, firstName, lastName, tag; OSErr theError = noErr; short count, item, ab, nick; Boolean gotOne; // we got at least one nickname out of the thing long offset; // offset to data in the vcard that's been left unparsed UseFeature (featureVCard); ab = GetAddressBook (targetInfo->nodeID); Zero (nickFromVCard); nickFromVCard.addresses = NuHandle (0); nickFromVCard.notes = NuHandle (0); theError = MemError (); count = MyCountDragItems (drag); for (item = 1; !theError && item <= count; ++item) { vcardData = nil; theError = MyGetDragItemData (drag, item, flavorTypeHFS, &dragData); if (!theError) { spec = (*dragData)->fileSpec; theError = SnarfRoman (&spec, &vcardData, 0); offset = 0; } do { if (!theError) { SetHandleBig (nickFromVCard.addresses, 0); SetHandleBig (nickFromVCard.notes, 0); nickFromVCard.numErrors = 0; theError = ParseVCard (vcardData, &offset, nil, VCardToNicknameItemProc, VCardToNicknameErrorProc, (long) &nickFromVCard); if (!theError && nickFromVCard.error) theError = dragNotAcceptedErr; } if (!offset) ZapHandle (vcardData); ZapHandle (dragData); // We should now have a nickname described as the addresses and notes. // We'll prep this data for its emergence as an official EUDORA NICKNAME!! // The addresses we've parsed are just raw text. Convert them into the BinAddr format we like. addresses = nil; if (!theError) theError = SuckAddresses (&addresses, nickFromVCard.addresses, true, true, false, nil); // Apply our name splitting algorithm and clean up the notes if (!theError) { MaybeApplySplittingAlgorithm (nickFromVCard.notes); Tr (nickFromVCard.notes, "\015", "\003"); // Derive the nickname by first looking joining the first and last names *nickname = 0; GetTaggedFieldValueStrInNotes (nickFromVCard.notes, GetRString (tag, ABReservedTagsStrn + abTagFirst), firstName); GetTaggedFieldValueStrInNotes (nickFromVCard.notes, GetRString (tag, ABReservedTagsStrn + abTagLast), lastName); if (*firstName && *lastName) JoinFirstLast (nickname, firstName, lastName); // If that didn't work, try the full name field if (!*nickname) GetTaggedFieldValueStrInNotes (nickFromVCard.notes, GetRString (tag, ABReservedTagsStrn + abTagName), nickname); // Last gasp... make a nickname out of the address if (!*nickname) { if (addresses && *addresses && **addresses) NickSuggest (addresses, nickname, fullName,true,0); } else { BeautifyFrom (nickname); SanitizeFN (nickname, nickname, NICK_BAD_CHAR, NICK_REP_CHAR, false); *nickname = MIN (*nickname, sizeof (Str31) - 1); } // Put the nickname into the address book (and cross our fingers) if (*nickname) { if (!ValidNickname (nick = AddNickname (addresses, nickFromVCard.notes, ab, nickname, false, nrDifferent, false))) theError = userCanceledErr; else gotOne = true; } } ZapHandle (addresses); } while (!theError && offset); } if (gotOne) { InvalidListView (&NickList); if (count == 1) { if ((*Aliases)[ab].collapsed) { VLNodeInfo data; LVGetItemWithNodeID (&NickList, MakeABNodeID (ab), &data); LVExpand (&NickList, MakeABNodeID (ab), data.name, true); } ABNickLVSelect (MakeNodeID (ab, nick), false); LVDraw (&NickList, nil, false, false); } } ZapHandle (nickFromVCard.addresses); ZapHandle (nickFromVCard.notes); return theError; } #ifdef NEVER // // NicknameFromVCardFile // // Create the nickname from a vCard file, returning handles containing // the addresses and notes of a nickname. // OSErr NicknameFromVCardFile (FSSpec *spec, Handle *addresses, Handle *notes) { NickFromVCardItemRec nickFromVCard; Handle vcardData; OSErr theError; theError = noErr; Zero (nickFromVCard); if (spec) { vcardData = nil; // Allocate a pair of empty handles for the notes and addresses nickFromVCard.addresses = NuHandle (0); nickFromVCard.notes = NuHandle (0); theError = MemError (); // Suck in all of the vCard data if (!theError) theError = Snarf (spec, &vcardData, 0); // Parse the vCard data using the VCardToNicknameItemProc to to the vCard-to-nickname transation if (!theError) theError = ParseVCard (vcardData, nil, VCardToNicknameItemProc, VCardToNicknameErrorProc, (long) &nickFromVCard); // We no longer need the vCard data ZapHandle (vcardData); } // If any errors occurred, zap the storage we allocated if (theError) { ZapHandle (nickFromVCard.addresses); ZapHandle (nickFromVCard.notes); } // Pass back the results *addresses = nickFromVCard.addresses; *notes = nickFromVCard.notes; return (theError); } #endif // // VCardToNicknameItemProc // // vCard item proc that builds a nickname from a vCard (wow!) // // We actually don't care about much of the stuff included in a vCard (though, we someday might). // This function can grow to some greater level of detail, but for now we make a simple address // book entry. // // Still, vCards can return very frustrating results to us. Take, for instance, the following pair // of items: // // TEL;HOME;WORK;VOICE;FAX:(858) 658-4063 // TEL;VOICE;FAX;HOME;WORK:(858) 658-4063 // // In both cases we have a fax and voice number used for home and work. So in our Eudora nickname // format we need to represent each of these lines as four unique attribute/value pairs (spread // across a pair of tabs in the Address Book). Because the order of the parameters is very // different in each example, we can't simply take each parameter one-by-one and easily determine // the appropriate attribute to be assigned. // // Instead, with each parsed item we'll build a list of home/work tag indices from which we'll // build each necessary attribute/value pair. In the above example, this list will look like so: // // abTagPhone abTagPhone2 // abTagFax abTagFax2 // // When we process this list we'll process BOTH the 'home' and 'work' tags for each. Had only one // of these tags (HOME or WORK) been present, we'd only process the appropriate index. // // vCard is capable of all kinds of screwy combinations -- some, as the spec admits, impossible to // interpret (mostly, these are encoding oddities). Because there is no formal schema, there's no // way to absolutely capture the spirit of a given vCard in our nickname format. Take a look at // the following: // // FN;HOME;FAX:John Purlia // // Huh? My "home" full name is a fax machine named "John Purlia"?? Or, this simpler combo: // // FN;HOME:John Purlia // FN;WORK;PREF:The Mad Scientist of VCard // FN;WORK;PREF:Super Toy Man, Master of all things mechanical // // So at work and at home I have different names. In fact, at work I have TWO names and both of // then are apparently the preferred way for people to communicate my name. This seems unreasonable // (not to mention incredibly pompous), but it's allowed by the vCard specification. So, since we // don't have a concept of multiple full names, we have to choose one. While we _could_ apply some // kind of logic to this problem and say, well, we'll use the first occurrence, or maybe we'll keep // those we don't use in notes as "AKA" fields, or maybe we'll see which (if any) is the preferred // name. Uh, no, sorry. There are too many fields and too many combinations. Instead, we'll use // the first one, ignoring all others. Tough. This also goes for non name fields that repeat in // a vCard. For example: // // TEL;WORK:(858) 658-4063 // TEL;WORK:(619) 890-6158 // // Ooooo... big man... you have two phones at work. Well, fine. We'll only assign the first as the // "Work Phone" in Eudora -- the second will end up in "Other Phone Numbers", and only because this // is a field that has a reasonable other place to put the data. If that's not the case, for example: // // TITLE:Director, Engineering // TITLE:Mac Programmer Guy // // For now we'll just ignore the second title (though I suppose it could be dumped into notes). // // Fields that have no reasonable nickname equivalent (GEO, TZ, etc) will for now be ignored. At some // future time we might dump this information into notes, but for now that will onyl server to clutter // the notes field. // OSErr VCardToNicknameItemProc (VCardItemPtr itemPtr, NickFromVCardItemRecPtr nickFromVCardItemPtr) { VCardParamPtr paramPtr; OSErr theError; Size valSize, addrSize; short numParams, i; Boolean home, work, ignored, tel, preferred; // Some initialization theError = noErr; ignored = false; tel = false; numParams = GetHandleSize (itemPtr->params) / sizeof (VCardParamRec); home = itemPtr->propertyFlags & vcPropFlagHome ? true : false; work = itemPtr->propertyFlags & vcPropFlagWork ? true : false; preferred = itemPtr->propertyFlags & vcPropFlagPreferred ? true : false; if (!home && !work) if (PrefIsSet (PREF_HOME_IS_NICER_THAN_WORK)) home = true; else work = true; // Any special decoding for the value? switch (itemPtr->encoding) { case vcValue7Bit: case vcValue8Bit: break; case vcValueQuotedPrintable: DecodeQuotedPrintable (itemPtr->value); break; case vcValueBase64: break; default: break; } valSize = GetHandleSize (itemPtr->value); if (!valSize) return (noErr); LDRef (itemPtr->value); // Properties we care about switch (itemPtr->property) { case vcKeyFN: theError = VCardToNicknameSetValue (nickFromVCardItemPtr->notes, abTagName, 0, true, false, *itemPtr->value, valSize, nickFieldIgnoreExisting, 0, nil); break; case vcKeyTitle: theError = VCardToNicknameSetValue (nickFromVCardItemPtr->notes, abTagTitle, 0, true, false, *itemPtr->value, valSize, nickFieldIgnoreExisting, 0, nil); break; case vcKeyTel: // Telephone numbers are special (or, at the very least, difficult). A parameter indicating the specific type // of phone might be present, so we handle those cases below. Or, a parameter might not be present -- so we have // to just note that we're working on a TEL property and handle it later if there are no specific phone properties. tel = true; break; case vcKeyEmail: // The preferred email address should go into the address expansion field -- everything else gets dumped into "other email". // If no preferred address is specified, we assume that the first one received is the preferred address. // Couple of things to note... When we find "the" preferred email address (at least, we hope only one is preferred) we take // anything previously assumed to be the preferred email address and dump it into the "other email" field, placing the new // data into the address expansion. If we find a second preferred email address, we repeat the swap, so it's possible that // duplicate addresses could end up in "other email", but this is a vCard problem (which is the lazy way of saying that we're // not going to save state just for the sake of badly formed vCard). if (preferred) { if (addrSize = GetHandleSize (nickFromVCardItemPtr->addresses)) { theError = VCardToNicknameSetValue (nickFromVCardItemPtr->notes, abTagOtherEmail, 0, true, false, LDRef (nickFromVCardItemPtr->addresses), addrSize, nickFieldAppendExisting, NICK_CONCAT_NEWLINE, nil); UL (nickFromVCardItemPtr->addresses); SetHandleBig (nickFromVCardItemPtr->addresses, 0); } if (!theError) theError = HandPlusHand (itemPtr->value, nickFromVCardItemPtr->addresses); } else // When we have an unpreferred address, check to see if the addresses expansion handle is empty. If it is, place it there. // If it is not, place it into "other email". if (!GetHandleSize (nickFromVCardItemPtr->addresses)) theError = HandPlusHand (itemPtr->value, nickFromVCardItemPtr->addresses); else theError = VCardToNicknameSetValue (nickFromVCardItemPtr->notes, abTagOtherEmail, 0, true, false, *itemPtr->value, valSize, nickFieldAppendExisting, NICK_CONCAT_NEWLINE, nil); break; case vcKeyURL: theError = VCardToNicknameSetValueOrOther (nickFromVCardItemPtr->notes, abTagWeb, abTagWeb2, abTagOtherWeb, home, work, *itemPtr->value, valSize, nickFieldIgnoreExisting, NICK_CONCAT_NEWLINE, &ignored); break; case vcKeyNote: theError = VCardToNicknameSetValue (nickFromVCardItemPtr->notes, abTagNote, 0, true, false, *itemPtr->value, valSize, nickFieldAppendExisting, NICK_CONCAT_NEWLINE, nil); break; case vcPOBox: case vcExtendedAddress: case vcStreet: theError = VCardToNicknameSetValue (nickFromVCardItemPtr->notes, abTagAddress, abTagAddress2, home, work, *itemPtr->value, valSize, nickFieldAppendExisting, NICK_CONCAT_NEWLINE, nil); break; case vcLocality: theError = VCardToNicknameSetValue (nickFromVCardItemPtr->notes, abTagCity, abTagCity2, home, work, *itemPtr->value, valSize, nickFieldIgnoreExisting, 0, nil); break; case vcRegion: theError = VCardToNicknameSetValue (nickFromVCardItemPtr->notes, abTagState, abTagState2, home, work, *itemPtr->value, valSize, nickFieldIgnoreExisting, 0, nil); break; case vcPostalCode: theError = VCardToNicknameSetValue (nickFromVCardItemPtr->notes, abTagZip, abTagZip2, home, work, *itemPtr->value, valSize, nickFieldIgnoreExisting, 0, nil); break; case vcCountry: theError = VCardToNicknameSetValue (nickFromVCardItemPtr->notes, abTagCountry, abTagCountry2, home, work, *itemPtr->value, valSize, nickFieldIgnoreExisting, 0, nil); break; case vcFamilyName: theError = VCardToNicknameSetValue (nickFromVCardItemPtr->notes, abTagLast, 0, true, false, *itemPtr->value, valSize, nickFieldIgnoreExisting, 0, nil); break; case vcGivenName: theError = VCardToNicknameSetValue (nickFromVCardItemPtr->notes, abTagFirst, 0, true, false, *itemPtr->value, valSize, nickFieldIgnoreExisting, 0, nil); break; case vcOrganizationName: case vcOrganizationUnits: theError = VCardToNicknameSetValue (nickFromVCardItemPtr->notes, 0, abTagCompany, false, true, *itemPtr->value, valSize, nickFieldAppendExisting, NICK_CONCAT_SEPARATOR, nil); break; } if (numParams) { paramPtr = *itemPtr->params; for (i = 0; i < numParams; ++i, ++ paramPtr) { switch (paramPtr->pProperty) { case vcKeyVoice: if (itemPtr->property == vcKeyTel) { tel = false; theError = VCardToNicknameSetValueOrOther (nickFromVCardItemPtr->notes, abTagPhone, abTagPhone2, abTagOtherPhone, home, work, *itemPtr->value, valSize, nickFieldIgnoreExisting, NICK_CONCAT_NEWLINE, &ignored); } break; case vcKeyFax: if (itemPtr->property == vcKeyTel) { tel = false; theError = VCardToNicknameSetValueOrOther (nickFromVCardItemPtr->notes, abTagFax, abTagFax2, abTagOtherPhone, home, work, *itemPtr->value, valSize, nickFieldIgnoreExisting, NICK_CONCAT_NEWLINE, &ignored); } break; case vcKeyMSG: // We basically ignore this one so that we don't accidentally repeat a number in the "other phone" field. We treat it like TEL break; case vcKeyCell: if (itemPtr->property == vcKeyTel) { tel = false; theError = VCardToNicknameSetValueOrOther (nickFromVCardItemPtr->notes, abTagMobile, abTagMobile2, abTagOtherPhone, home, work, *itemPtr->value, valSize, nickFieldIgnoreExisting, NICK_CONCAT_NEWLINE, &ignored); } break; } } } // If we're working on a telephone property that was not assigned by an existing parameter, assume it is a regular phone if (!theError && tel) theError = VCardToNicknameSetValueOrOther (nickFromVCardItemPtr->notes, abTagPhone, abTagPhone2, abTagOtherPhone, home, work, *itemPtr->value, valSize, nickFieldIgnoreExisting, NICK_CONCAT_NEWLINE, &ignored); UL (itemPtr->value); return (theError); } Boolean VCardToNicknameErrorProc (Handle data, VCardItemPtr itemPtr, NickFromVCardItemRecPtr nickFromVCardItemPtr, VCardErrorType error, long offset) { Boolean shouldWeQuit; // We'll be extra tough on data that does not contain a 'BEGIN' keyword or // any discerable vCard data (but leaving out 'END' is dandy. ++nickFromVCardItemPtr->numErrors; if (shouldWeQuit = (error == vcMissingBegin || error == vcMissingVcard)) { nickFromVCardItemPtr->error = error; nickFromVCardItemPtr->offset = offset; } return (PrefIsSet (PREF_VCARD_QUIT_ON_ERROR) || shouldWeQuit); } OSErr VCardToNicknameSetValue (Handle notes, short homeTagIndex, short workTagIndex, Boolean home, Boolean work, Ptr value, long length, NickFieldSetValueType setValue, short separatorIndex, Boolean *ignored) { Str255 tag; OSErr theError; theError = noErr; if (home) theError = SetTaggedFieldValueInNotes (notes, GetRString (tag, ABReservedTagsStrn + homeTagIndex), value, length, setValue, separatorIndex, ignored); if (work) theError = SetTaggedFieldValueInNotes (notes, GetRString (tag, ABReservedTagsStrn + workTagIndex), value, length, setValue, separatorIndex, ignored); return (theError); } OSErr VCardToNicknameSetValueOrOther (Handle notes, short homeTagIndex, short workTagIndex, short otherTagIndex, Boolean home, Boolean work, Ptr value, long length, NickFieldSetValueType setValue, short separatorIndex, Boolean *ignored) { OSErr theError; theError = VCardToNicknameSetValue (notes, homeTagIndex, workTagIndex, home, work, value, length, setValue, 0, ignored); if (*ignored && !theError) theError = VCardToNicknameSetValue (notes, otherTagIndex, 0, true, false, value, length, setValue, separatorIndex, nil); return (theError); } // // DecodeQuotedPrintable // // Remove occurences of "=\r" // Replace occurences of "=0D" and "=0A" with '\r' // void DecodeQuotedPrintable (Handle value) { Ptr spot, end, copySpot; spot = *value; end = spot + GetHandleSize (value); copySpot = spot; while (spot < end) { if (*spot == '=') { if (spot + 1 < end && !memcmp (spot, "=\r", 2)) spot += 2; else if (spot + 5 < end && !memcmp (spot, "=0D=0A", 6)) { spot += 6; *copySpot++ = '\r'; } else if (spot + 2 < end && !memcmp (spot, "=0D", 3)) { spot += 3; *copySpot++ = '\r'; } else if (spot + 2 < end && !memcmp (spot, "=0A", 3)) { spot += 3; *copySpot++ = '\r'; } else *copySpot++ = *spot++; } else *copySpot++ = *spot++; } SetHandleBig (value, copySpot - *value); } #endif OSErr GetNickFlavorDragData (Handle dragData, long *offset, short *ab, short *nick, PStr nickname, Handle *addresses, Handle *notes) { Ptr dragDataPtr; OSErr theError; long dataSize; theError = noErr; dragDataPtr = LDRef (dragData) + *offset; // The address book and nickname BlockMoveData (dragDataPtr, ab, sizeof (*ab)); dragDataPtr += sizeof (*ab); BlockMoveData (dragDataPtr, nick, sizeof (*nick)); dragDataPtr += sizeof (*nick); // Make sure these are valid indices -- otherwise we might be dealing with garbage // Commented out for now because this does not seem to be used // if (*ab < 0 || *ab >= NAliases) // return (dragNotAcceptedErr); // if (*nick < 0 || *nick >= (GetHandleSize_ ((*Aliases)[*ab].theData) / sizeof (NickStruct))) // return (dragNotAcceptedErr); // The nickname itself PCopy (nickname, dragDataPtr); dragDataPtr += *nickname + 1; // Addresses BlockMoveData (dragDataPtr, &dataSize, sizeof (dataSize)); dragDataPtr += sizeof (dataSize); if (dataSize && addresses) theError = PtrToHand (dragDataPtr, addresses, dataSize); dragDataPtr += dataSize; // Notes if (!theError) { BlockMoveData (dragDataPtr, &dataSize, sizeof(dataSize)); dragDataPtr += sizeof (dataSize); if (dataSize && notes) theError = PtrToHand (dragDataPtr, notes, dataSize); dragDataPtr += dataSize; } UL (dragData); *offset = dragDataPtr - *dragData; return (theError); } // // BuildDragData // // Build a data handle from the selected nicknames. Based somewhat on the old NickDragToData // but using the new list view and expanded nickname schtuff. // OSErr BuildDragData (SendDragDataInfo *pSendData, Handle *dragData) { Accumulator drag; OSErr theError; short ab, nick, lastAB, count, index; theError = AccuInit (&drag); if (!theError) { count = LVCountSelection (&NickList); // Take a trip through all the selections lastAB = kAddressBookUndefined; for (index = 1; index <= count && !theError && !drag.err; ++index) if (GetSelectedABNick (index, &ab, &nick)) { CycleBalls(); if (lastAB != ab && ValidNickname (nick)) theError = BuildNicknameDragData (pSendData, &drag, ab, nick); else theError = BuildAddressBookDragData (pSendData, &drag, lastAB = ab); } } if (!theError) { AccuTrim (&drag); *dragData = drag.data; drag.data = nil; } AccuZap (drag); return (theError); } OSErr BuildNicknameDragData (SendDragDataInfo *pSendData, AccuPtr drag, short ab, short nick) { OSErr theError; theError = noErr; switch (pSendData->flavor) { case A822_FLAVOR: theError = Build822FlavorDragData (drag, ab, nick); break; case kNickDragType: theError = BuildNickFlavorDragData (drag, ab, nick); break; case 'TEXT': theError = BuildTextFlavorDragData (drag, ab, nick); break; #ifdef VCARD case SPEC_FLAVOR: if (IsVCardAvailable ()) theError = BuildSpecFlavorDragData (pSendData, drag, ab, nick); break; #endif } return (theError); } OSErr BuildAddressBookDragData (SendDragDataInfo *pSendData, AccuPtr drag, short ab) { OSErr theError; short totalNicks, nick; theError = noErr; totalNicks = (*Aliases)[ab].theData ? (GetHandleSize_ ((*Aliases)[ab].theData) / sizeof (NickStruct)) : 0; for (nick = 0; nick < totalNicks && !theError; nick++) theError = BuildNicknameDragData (pSendData, drag, ab, nick); return (theError); } OSErr Build822FlavorDragData (AccuPtr drag, short ab, short nick) { Handle data; OSErr theError; theError = noErr; if (data = GetAliasExpansionFor822Flavor (ab, nick)) { if (drag->offset) theError = AccuAddChar (drag, ','); if (!theError) theError = AccuAddHandle (drag, data); ZapHandle (data); } return (theError); } OSErr BuildNickFlavorDragData (AccuPtr drag, short ab, short nick) { Str31 name; Handle data; OSErr theError; long dataSize; // The address book theError = AccuAddPtr (drag, (void *) &ab, sizeof (ab)); // The nickname if (!theError) theError = AccuAddPtr (drag, (void *) &nick, sizeof (nick)); // name if (!theError) { GetNicknameNamePStr (ab, nick, name); theError = AccuAddPtr (drag, name, *name + 1); } // addresses if (!theError) { data = GetNicknameData (ab, nick, true, true); dataSize = data ? GetHandleSize (data) : 0; theError = AccuAddPtr (drag, (void *) &dataSize, sizeof (dataSize)); } if (data && !theError) theError = AccuAddHandle (drag, data); // notes if (!theError) { data = GetNicknameData (ab, nick, false, true); dataSize = data ? GetHandleSize (data) : 0; theError = AccuAddPtr (drag, (void *) &dataSize, sizeof (dataSize)); } if (data && !theError) theError = AccuAddHandle (drag, data); return (theError); } // // BuildTextFlavorDragData // OSErr BuildTextFlavorDragData (AccuPtr drag, short ab, short nick) { AttributeValueHandle avPairs; AttributeValuePtr avPairPtr; TabFieldHandle fields; Str31 name, tag, shortName; BinAddrHandle expansion; Handle addresses, notes, leftovers; OSErr theError; long unrequestedTypesToBePutInLeftovers; short count, i; // Data we will find incredibly useful later leftovers = nil; addresses = nil; expansion = nil; fields = Tabs && AB.inited ? GetTabFieldStrings (Tabs, tabTagName) : GetTabFieldsFromResources (tabTagName, CREATOR, kNickTabType); notes = GetNicknameData (ab, nick, false, true); unrequestedTypesToBePutInLeftovers = PrefIsSet (PREF_INCLUDE_HIDDEN_NICK_FIELDS_IN_DRAGS) ? (avPairUnknown | avPairHidden) : avPairUnknown; avPairs = notes ? ParseAllAttributeValuePairs (notes, &leftovers, avPairKnown, unrequestedTypesToBePutInLeftovers) : nil; theError = nil; // Put in a blank line in between nicknames if (drag->offset) theError = AccuAddChar (drag, kDragDataDelim); // The nickname itself if (!theError) theError = AccuAddRes (drag, ALIAS_A_LABEL); if (!theError) theError = AccuAddChar (drag, ' '); if (!theError) theError = AccuAddStr (drag, GetNicknameNamePStr (ab, nick, name)); if (!theError) theError = AccuAddChar (drag, kDragDataDelim); // All the email addresses -- which is the expansion of the nickname itself. if (!theError) theError = PtrToHand (name, &addresses, *name + 1); if (!theError) theError = PtrPlusHand ("", addresses, 1); if (!theError) theError = ExpandAliases (&expansion, addresses, 0, true); if (!theError) CommaList (expansion); if (expansion) { if (!theError) theError = AccuAddStr (drag, FindShortNameWithTag (Tabs, GetRString (tag, ABReservedTagsStrn + abTagEmail), shortName)); if (!theError) theError = AccuAddChar (drag, ' '); if (!theError) theError = AccuAddHandle (drag, expansion); if (!theError) theError = AccuAddChar (drag, kDragDataDelim); } if (!theError && fields && notes && avPairs) { // Everything else including the kitchen sink if (!theError) { avPairPtr = LDRef (avPairs); LDRef (notes); count = GetHandleSize (avPairs) / sizeof (AttributeValueRec); for (i = 0; i < count && !theError; ++i, ++avPairPtr) { MakePStr (tag, *notes + avPairPtr->attributeOffset, avPairPtr->attributeLength); FindShortNameWithTag (Tabs, tag, name); if (*name) { theError = AccuAddStr (drag, name); if (!theError) theError = AccuAddChar (drag, ' '); if (!theError) theError = AccuAddTrPtr (drag, *notes + avPairPtr->valueOffset, avPairPtr->valueLength,"\002\003",">\015"); if (!theError) theError = AccuAddChar (drag, kDragDataDelim); } } UL (notes); } // Leftovers! Mmmm, mmm, good! if (leftovers && GetHandleSize (leftovers)) { if (!theError) theError = AccuAddRes (drag, ALIAS_N_LABEL); if (!theError) theError = AccuAddChar (drag, ' '); if (!theError) theError = AccuAddTrHandle (drag, leftovers,"\002",">"); if (!theError) theError = AccuAddChar (drag, kDragDataDelim); } } // Cleanup ZapHandle (fields); ZapHandle (avPairs); ZapHandle (leftovers); ZapHandle (expansion); ZapHandle (addresses); return (theError); } #ifdef VCARD // // BuildSpecFlavorDragData // OSErr BuildSpecFlavorDragData (SendDragDataInfo *pSendData, AccuPtr drag, short ab, short nick) { AEDesc dropLocation; FSSpec spec; Handle vcardData, notes; OSErr theError; theError = noErr; // Make a vCard from this nickname if (notes = GetNicknameData (ab, nick, false, true)) Tr (notes, "\003", "\015"); if (vcardData = MakeVCard (GetNicknameData (ab, nick, true, true), notes)) { NullADList (&dropLocation, nil); theError = GetDropLocation (pSendData->drag, &dropLocation); if (!theError) theError = GetDropLocationDirectory (&dropLocation, &spec.vRefNum, &spec.parID); if (!theError) { MakeVCardFileName (ab, nick, spec.name); theError = UniqueSpec (&spec, 31); } if (!theError) { FSpCreateResFile (&spec, CREATOR, VCARD_TYPE, smSystemScript); WhackFinder (&spec); theError = ResError (); } // Write the vCard data into the file if (!theError) theError = Blat (&spec, vcardData, false); if (!theError) theError = AccuAddPtr (drag, &spec, sizeof (FSSpec)); DisposeADList (&dropLocation,nil); ZapHandle (vcardData); } else theError = MemError (); return (theError); } #endif // // DropNicknameOntoAddressBook // // Handle nickname drops onto address books. Returns true if the drop successfully // completed, false if not. // Boolean DropNicknameOntoAddressBook (VLNodeID **movedList, VLNodeInfo *targetInfo, short ab, short nick, Boolean copy) { VLNodeID nodeID; BinAddrHandle mungedAddresses; short targetAB, targetNick, newNick; Boolean lookingGood; lookingGood = false; // We are pessimists mungedAddresses = nil; GetABNick (targetInfo->nodeID, &targetAB, &targetNick); if (ab != targetAB && ValidAddressBook (targetAB) && !ValidNickname (targetNick)) if (lookingGood = ValidNickname (newNick = MoveNickname (ab, nick, targetAB, copy))) { nodeID = MakeNodeID (targetAB, newNick); PtrPlusHand (&nodeID, movedList, sizeof (nodeID)); } return (lookingGood); } // // DropAddressBook // // Handle address book drops onto other address books. Returns true if the drop successfully // completed, false if not. // Boolean DropAddressBook (VLNodeID **movedList, VLNodeInfo *targetInfo, short ab, Boolean copy) { VLNodeID nodeID; short targetAB, targetNick, abNick, totalNicks, newNick; Boolean lookingGood; lookingGood = false; // We are pessimists GetABNick (targetInfo->nodeID, &targetAB, &targetNick); if (ab != targetAB) { GetABNick (targetInfo->nodeID, &targetAB, &targetNick); if (ValidAddressBook (targetAB) && !ValidNickname (targetNick)) { totalNicks = (*Aliases)[ab].theData ? (GetHandleSize_ ((*Aliases)[ab].theData) / sizeof (NickStruct)) : 0; for (abNick = 0; abNick < totalNicks; abNick++) if (lookingGood = ValidNickname (newNick = MoveNickname (ab, abNick, targetAB, copy))) { nodeID = MakeNodeID (targetAB, newNick); PtrPlusHand (&nodeID, movedList, sizeof (nodeID)); } } } return (lookingGood); } // // MoveNickname // // Returns the index of the nickname as it appears in it's new address book, or // kNickUndefined if the move failed. // short MoveNickname (short ab, short nick, short targetAB, Boolean copy) { BinAddrHandle mungedAddresses; Handle addresses, notes; Str31 name; short newNick; Boolean lookingGood; if (!ValidAddressBook (targetAB)) return (kNickUndefined); lookingGood = true; mungedAddresses = nil; notes = GetNicknameData (ab, nick, false, true); if (addresses = GetNicknameData (ab, nick, true, true)) if (SuckAddresses (&mungedAddresses, addresses, true, true, false, nil)) lookingGood = false; if (lookingGood) { // Add the nickname into the target lookingGood = ValidNickname (newNick = AddNickname (mungedAddresses, notes, targetAB, GetNicknameNamePStr (ab, nick, name), false, nrDifferent, false)); // Now, if that worked out okay, mark the nickname from its original address book as deleted (unless we were performing a copy) if (lookingGood && !copy && !(*Aliases)[ab].ro && (*Aliases)[ab].theData) { (*((*Aliases)[ab].theData))[nick].addressesDirty = true; (*((*Aliases)[ab].theData))[nick].deleted = true; SetAliasDirty(ab); } } ZapHandle (mungedAddresses); return (lookingGood ? newNick : kNickUndefined); } /************************************************************************ * DoViewByMenu - change the way the list is sorted ************************************************************************/ void DoViewByMenu (void) { Str255 oldViewByString, newViewByString; short numABs, ab, abDisplayed, nickDisplayed; GetPref (oldViewByString, PREF_NICK_VIEW_BY); GetMenuItemText (PopUpMenuH (CtlViewBy), GetControlValue (CtlViewBy), newViewByString); if (!StringSame (oldViewByString, newViewByString)) { abDisplayed = AB.abDisplayed; nickDisplayed = AB.nickDisplayed; numABs = NAliases; for (ab = 0; ab < numABs; ab++) ZapHandle ((*Aliases)[ab].sortData); InvalidListView (&NickList); // Reselect the previous items if (!(abDisplayed == kAddressBookUndefined && nickDisplayed == kNickUndefined)) ABNickLVSelect (MakeNodeID (abDisplayed, nickDisplayed), true); SetPref (PREF_NICK_VIEW_BY, newViewByString); } } /************************************************************************ * DoNewNickname - make a new nickname ************************************************************************/ void DoNewNickname (long modifiers) { VLNodeInfo data; Str31 name, massagedName; Handle addresses, notes, shortAddresses; short ab, nick; // Put the nickname in the first selected address book... // ... or in the default address book if (LVGetItem (&NickList, 1, &data, true)) GetABNick (data.nodeID, &ab, &nick); else if (!LVGetItemWithNodeID (&NickList, MakeABNodeID (ab = FindAddressBookType (eudoraAddressBook)), &data)) return; // If a nickname (by itself) is selected, and the option key is being held down, we'll clone that nickname *name = 0; addresses = nil; notes = nil; shortAddresses = nil; if ((modifiers & optionKey) && LVCountSelection (&NickList) == 1 && ValidNickname (nick)) { PCopy (name, data.name); if (addresses = GetNicknameData (ab, nick, true, true)) SuckPtrAddresses (&shortAddresses, LDRef (addresses), GetHandleSize (addresses), true, true, false, nil); notes = GetNicknameData (ab, nick, false, true); // Make copies of the existing notes to be passed to NewNickLow if (notes) HandToHand (¬es); } MakeUniqueNickname (ab, name); if ((nick = NewNickLow (shortAddresses, notes, ab, name, false, nrAdd, false)) != kNickUndefined) { // If this address book is collapsed, expand it if ((*Aliases)[ab].collapsed) LVExpand (&NickList, MakeABNodeID (ab), data.name, true); InvalidListView (&NickList); // Massage the item name so it can be found in the list view GetListNameBasedOnTag (SortTag, ab, nick, massagedName); // Rename it ABSetKeyboardFocus (focusNickList); LVRename (&NickList, MakeNodeID (ab, nick), massagedName, false, !PrefIsSet (PREF_ALLOW_NICK_RENAME_IN_LIST)); // Allow user to rename the untitled nickname if (!PrefIsSet (PREF_ALLOW_NICK_RENAME_IN_LIST)) { ABSetKeyboardFocus (focusNickField); PeteSelectAll (Win, Win->pte); } } ZapHandle (shortAddresses); } /************************************************************************ * DoNewNickname - make a new nickname ************************************************************************/ void DoNewAddressBook (void) { FSSpec spec, folderSpec; short ab; SubFolderSpec (NICK_FOLDER,&folderSpec); // Make a unique "untitled" name MakeUniqueUntitledSpec (folderSpec.vRefNum, folderSpec.parID, UNTITLED_ADDRESS_BOOK, &spec); // Make the new file ab = NickMakeNewFile (spec.name); if (ValidAddressBook (ab)) { InvalidListView (&NickList); LVRename (&NickList, MakeABNodeID (ab), spec.name, false, false); // Allow user to rename the untitled address book } } void DoHorizontalZoom (MyWindowPtr win) { WindowPtr winWP; Rect contR; winWP = GetMyWindowWindowPtr (win); win->saveSize = true; utl_InvalGrow (winWP); if (Collapsed) { SetWinMinSize (Win, MinListWidth + MinTabWidth, kMinNickWinHeight); SetPrefLong (PREF_NICK_COLLAPSED, 0); contR = win->contR; contR.right += Collapsed - 2; SanitizeSize (win, &contR); // Show the things on the right side (note that the tabs, since not really hidden, move into place when we resize ShowControl (NickField); ShowControl (CtlRecipient); ShowControl (CtlDoNotSync); SizeWindow (winWP, contR.right, contR.bottom, false); Collapsed = 0; } else { SetWinMinSize (Win, MinListWidth, kMinNickWinHeight); SetPrefLong (PREF_NICK_COLLAPSED, Collapsed = win->contR.right - VertDivider.left + 1); // Hide the things on the right side MoveLabelField (NickField, CNTL_OUT_OF_VIEW, CNTL_OUT_OF_VIEW, ControlWi (NickField), ControlHi (NickField)); MoveMyCntl (CtlRecipient, CNTL_OUT_OF_VIEW, CNTL_OUT_OF_VIEW, ControlWi (CtlRecipient), ControlHi (CtlRecipient)); MoveMyCntl (CtlDoNotSync, CNTL_OUT_OF_VIEW, CNTL_OUT_OF_VIEW, ControlWi (CtlDoNotSync), ControlHi (CtlDoNotSync)); MoveTab (Tabs, CNTL_OUT_OF_VIEW, CNTL_OUT_OF_VIEW, ControlWi (Tabs), ControlHi (Tabs)); // Move the focus to the list ABSetKeyboardFocus (focusNickList); SizeWindow (winWP, VertDivider.left, win->contR.bottom, false); } MyWindowDidResize (win, nil); } void DoRecipientList (ControlHandle theControl) { MyWindowPtr win = GetWindowMyWindowPtr(GetControlOwner(theControl)); VLNodeInfo data; Str31 name; short value, count, i, ab, nick; // Flip the checkbox itself value = GetControlValue (theControl); SetControlValue (theControl, value = (value == kControlCheckBoxUncheckedValue || value == kControlCheckBoxMixedValue ? kControlCheckBoxCheckedValue : kControlCheckBoxUncheckedValue)); // Walk through all the selected list items count = LVCountSelection (&NickList); // Take a trip through all the selections looking for relevant conditions for (i = 1; i <= count; ++i) { GetSelectedABNickNameData (i, &ab, &nick, name, &data); if (nick != kNickUndefined) { GetNicknameNamePStr (ab, nick, name); if (value == kControlCheckBoxCheckedValue) AddStringAsTo (name); else RemoveFromTo (name); } data.iconID = ABSetIcon (ab, nick); LVSetItemWithNodeID (&NickList, MakeNodeID (ab, nick), &data, true); } win->isDirty = true; } void DoDoNotSync (ControlHandle theControl) { MyWindowPtr win = GetWindowMyWindowPtr(GetControlOwner(theControl)); VLNodeInfo data; Str31 name; Handle notes; short value, count, i, ab, nick; Boolean notesExist; // Flip the checkbox itself value = GetControlValue (theControl); SetControlValue (theControl, value = (value == kControlCheckBoxUncheckedValue || value == kControlCheckBoxMixedValue ? kControlCheckBoxCheckedValue : kControlCheckBoxUncheckedValue)); // Walk through all the selected list items count = LVCountSelection (&NickList); // Take a trip through all the selections looking for relevant conditions for (i = 1; i <= count; ++i) { GetSelectedABNickNameData (i, &ab, &nick, name, &data); if (nick != kNickUndefined) { GetNicknameNamePStr (ab, nick, name); if (value == kControlCheckBoxCheckedValue) { notes = GetNicknameData (ab, nick, false, true); if (!(notesExist = notes ? true : false)) notes = NuHandle (0); if (notes) if (!SetNicknameChangeBit (notes, changeBitPrivate, false)) ReplaceNicknameNotes (ab, name, notes); if (!notesExist) ZapHandle (notes); } else ClearNicknameChangeBits (ab, nick, changeBitPrivate); } } win->isDirty = true; } // // ABNickLVSelect // // Wrapper around list view's LVSelect taking into account the current View By sort order // Boolean ABNickLVSelect (VLNodeID nodeID, Boolean addToSelection) { Str31 massagedName; return (LVSelect (&NickList, nodeID, GetListNameBasedOnTag (SortTag, GetAddressBook (nodeID), GetNickname (nodeID), massagedName), addToSelection)); } // // ABRename // // We're always dealing with nicknames here, even though the list might be sorted // by some other means // // WARNING - after calling this to rename a nickname file, the index (ab) may no longer // be valid. SortAddressBookList() is called here which may reorder the Aliases list. // Don't trust ab after you call this routine on a nickname file! - jdboyd 12/18/03 Boolean ABRename (short ab, short nick, StringPtr newName) { VLNodeInfo data; VLNodeID nodeID; Str31 oldName; Handle notes; OSErr theError; Boolean notesExist; Str63 abName; // If we haven't been given a valid nickname, bail on outta here nodeID = MakeNodeID (ab, nick); if (!LVGetItemWithNodeID (&NickList, nodeID, &data)) return (false); *newName = MIN (*newName, sizeof (Str31) - 1); if (*newName && !ExactlyEqualString (newName, GetNicknameNamePStr (ab, nick, oldName))) { PCopy (data.name, newName); // Is it a nickname file? if (nick == kNickUndefined) { Str31 cleanName; SanitizeFN(cleanName,newName,MAC_FN_BAD,MAC_FN_REP,false); if (EqualStrRes(cleanName,ALIAS_FILE) || EqualStrRes(cleanName,USA_EUDORA_NICKNAMES) || EqualStrRes(cleanName,FILE_ALIAS_NICKNAMES)) theError = dupFNErr; else theError = FSpRename (&(*Aliases)[ab].spec, cleanName); if (!theError) { PCopy ((*Aliases)[ab].spec.name, cleanName); SortAddressBookList (); // sorting the list may have moved the address book. Find it again. jdboyd 12/18/03 for (ab = NAliases-1; ab>=0; ab--) { PCopy(abName,(*Aliases)[ab].spec.name); if (StringSame(abName,cleanName)) break; } // We'll need to rename the nickname's photo folder in the photo album as well } else { FileSystemError (CANT_RENAME_ERR, (*Aliases)[ab].spec.name, theError); return (false); } } else { // Check to see whether or not we already have a nickname with this name if (BadNickname (newName, oldName, ab, nick, true, true, false) != nrOk) return (false); // Make sure the state of the recipient list is maintained if (IsNicknameOnRecipList (ab, nick)) { RemoveFromTo (oldName); AddStringAsTo (newName); } // Rename this in the toolbar as well TBRenameNickButton (oldName, newName); // Let's rename a nickname SetNickname (ab, nick, newName); LVSetItemWithNodeID (&NickList, nodeID, &data, false); // Update the nickname field SetLabelFieldString (NickField, newName); } // Need to resort the list InvalidListView (&NickList); // Massage the item name so it can be found in the list view ABNickLVSelect (data.nodeID, true); SetAliasDirty(ab); // 1/25/01 (jp) We have a problem in the conduit when nicknames are changed // and only the 'NNam' resource is changed on disk. The 'alias' left in the // data fork reflects the old nickname. To fix this, we're going to read in // the nickname's notes and mark them dirty, which seems to force the 'alias' // lines to be written to disk when we save. if (ValidNickname (nick)) { notes = GetNicknameData (ab, nick, false, true); if (!(notesExist = notes ? true : false)) notes = NuHandle (0); if (notes) ReplaceNicknameNotes (ab, newName, notes); if (!notesExist) ZapHandle (notes); } } return (true); } /************************************************************************ * ABRemove - delete selected nicknames and address books ************************************************************************/ void ABRemove(void) { VLNodeInfo data; Str31 name; short ab, nick, i; Boolean weDid; // well? weDid = false; ABSetKeyboardFocus (focusNickList); // Save info about the first selected row so we can reselect // the item now in this row after the remove has been completed. LVGetItem (&NickList, 1, &data, true); // Check for ro for (i = LVCountSelection (&NickList); i; --i) if (GetSelectedABNick (i, &ab, &nick) && (*Aliases)[ab].ro) { WarnUser(ITS_LOCKED_DUMMY,0); return; } // Remove items in reverse list order for (i = LVCountSelection (&NickList); i; --i) { if (GetSelectedABNick (i, &ab, &nick)) { // Is it a nickname file? if (nick == kNickUndefined) weDid = RemoveAddressBook (ab); else { GetNicknameNamePStr (ab, nick, name); if (IsNicknameOnRecipList (ab, nick)) RemoveFromTo (name); if (weDid = RemoveNickname (ab, nick)) TBRemoveDefunctNicknameButton (name); SetAliasDirty(ab); } } } if (weDid) { InvalidListView (&NickList); if (LVGetItem (&NickList, data.rowNum, &data, false)) if (!data.isParent) LVSelect (&NickList, data.nodeID, data.name, true); } } // // ABMaybeRenameNickname // Boolean ABMaybeRenameNickname (MyWindowPtr win) { Str255 nickname; Boolean fResults; // Patch up our focus problem... if (AB.abDisplayed<0 || AB.nickDisplayed<0) return true; fResults = true; // If the focus is in the nickname field, check to see if we need to rename the nickname itself if (NickFocus == focusNickField) if (!ABRename (AB.abDisplayed, AB.nickDisplayed, GetLabelFieldString (NickField, nickname))) { GetNicknameNamePStr (AB.abDisplayed, AB.nickDisplayed, nickname); SetLabelFieldString (NickField, nickname); PeteSelectAll (win, win->pte); fResults = false; } return (fResults); } Boolean ABDisplayNicknameInTab (short ab, short nick) { Str31 nickname, massagedName; OSErr theError; Boolean validNickname, changedDisplay; theError = noErr; changedDisplay = false; if (LVCountSelection (&NickList) != 1) { ab = kAddressBookUndefined; nick = kNickUndefined; } if (ab != AB.abDisplayed || nick != AB.nickDisplayed) { validNickname = (ab != kAddressBookUndefined && nick != kNickUndefined); // Nickname field nickname[0] = 0; if (validNickname) GetNicknameNamePStr (ab, nick, nickname); SetLabelFieldString (NickField, nickname); // Let the tab ready itself for a new nickname ClearTab (Tabs); // Now we fill in the cleared fields with any tagged data if (validNickname) theError = PopulateTabs (Tabs, ab, nick); // clean all the dirty stuff so we're ready for user input ABClean (); AB.abDisplayed = ab; AB.nickDisplayed = nick; changedDisplay = true; } // Uh oh... if we weren't able to populate the tabs, unselect the item and tell the user if (theError) if (validNickname) { // Massage the item name so it can be found in the list view GetListNameBasedOnTag (SortTag, ab, nick, massagedName); LVUnselect (&NickList, MakeNodeID (ab, nick), massagedName, true); } return (changedDisplay); } // // PopulateTabs // // ¥ Find the relevant nickname data in notes // ¥ Parse tags from the nickname data // ¥ For each found tag, look for this tag in an object within each tab pane // ¥ Set the value of the object to the value associated with the tag in the data // ¥ Set any other object values to be empty // OSErr PopulateTabs (ControlHandle tabControl, short ab, short nick) { TabObjectPtr objectPtr; Accumulator leftovers; // Untagged or unrecognized tagged information we find PETEHandle pte; Handle temp, addresses, notes; Str255 tag; UPtr notesPtr, newPtr, attribute, value; OSErr theError; Size notesSize; long attributeLength, valueLength; addresses = nil; notes = nil; theError = AccuInit (&leftovers); // The addresses field. We'll try to suck addresses from the handle we've retrieved. If we're not able // to get addresses we'll just use the content of the handle in it's entirety if (!theError) { GetRString (tag, ABReservedTagsStrn + abTagEmail); if (temp = GetNicknameData (ab, nick, true, true)) { theError = SuckAddresses (&addresses, temp, true, true, false, nil); if (!theError) if (addresses) { if (!PrefIsSet(PREF_NICK_NOSORT)) SortBinAddr(addresses); FlattenListWith (addresses, '\015'); SetObjectValueWithTag (tabControl, tag, LDRef (addresses), GetHandleSize (addresses)); UL (addresses); } } if (!theError && !addresses && temp) { SetObjectValueWithTag (tabControl, tag, LDRef (temp), GetHandleSize (temp)); UL (temp); } } ZapHandle (addresses); // Find the relevant nickname data in notes if (!theError) { if (temp = GetNicknameData (ab, nick, false, true)) { notes = temp; theError = MyHandToHand (¬es); } } if (!theError && notes) { // Clean things up a bit Tr (notes, "\003", "\015"); // Walk through the 'notes', looking for attribute/value pairs. notesSize = GetHandleSize (notes); notesPtr = LDRef (notes); // Go fish... Dangerous -- but we are being very, very careful to not use any pointer into the notes after we have unlocked the handle while (!theError && (newPtr = ParseAttributeValuePair (notesPtr, notesSize - (notesPtr - *notes), &attribute, &attributeLength, &value, &valueLength))) { // I got one! MakePPtr (tag, attribute, attributeLength); // If anything appears between this tag and the previous tag, put it in leftovers theError = AppendLeftoverText (&leftovers, notesPtr, attribute - 2); // Try to map this tag to one of our fields if (!theError && !SetObjectValueWithTag (tabControl, tag, value, valueLength)) { // If there wasn't a matching tag, and this is not one of our hidden tags, // save this information in our 'leftovers' if (!FindSTRNIndex (ABHiddenTagsStrn, tag)) { if (leftovers.offset) theError = AccuAddChar (&leftovers, '\015'); if (!theError) theError = AccuAddPtr (&leftovers, attribute - 1, value - attribute + valueLength + 2); } } notesPtr = newPtr; } // Any leftovers at the end of the note? if (!theError) theError = AppendLeftoverText (&leftovers, notesPtr, *notes + notesSize - 1); } // Once we're done setting up all the found tab fields, append the leftovers to the notes field if (!theError && leftovers.offset) { AccuTrim (&leftovers); if (objectPtr = FindObjectWithTag (tabControl, GetRString (tag, ABReservedTagsStrn + abTagNote))) if (pte = GetLabelFieldPete (objectPtr->control)) theError = PeteAppendText (LDRef (leftovers.data), GetHandleSize (leftovers.data), pte); UnlockObject (objectPtr); } ZapHandle (notes); AccuZap (leftovers); return (theError); } // // ReplaceNicknameData // // Replace an existing nickname with new (new! new! new!) minty fresh data culled from changes in the address book // void ReplaceNicknameData (ControlHandle tabControl, short ab, short nick) { NicknameDataRec nickData; AttributeValueHandle avPairs; AttributeValuePtr avPairPtr; TabObjectPtr objectPtr; VLNodeInfo data; VLNodeID nodeID; Str255 name, tag; Handle addresses, notes, temp2Handle; short count, i; Boolean noAddresses, oldGroup; // Doesn't make much sense to replace the data for an address bookn does it? if (nick == kNickUndefined) return; // We will _only_ replace the nickname data if this is _really_ the displayed nickname if (ab == AB.abDisplayed || nick == AB.nickDisplayed) { // Funky code to see if the email expansion field is empty. If it is, we lie and say it is // dirty (even if it is not) so that we have a chance to initialize the contents of the address // handle to a comma to maintain compatibility with the old code which requires something in // addresses in order for a nickname to be written out into the nickname file (grumble, grumble...) // Don't do this on locked address book files. Who cares if the old code can't write the nickname // back out to the file? This address book is read-only and we're not going to be saving changes. // jdboyd 3/24/04 noAddresses = false; if (!(*Aliases)[ab].ro) { if (objectPtr = FindObjectWithTag (tabControl, GetRString (tag, ABReservedTagsStrn + abTagEmail))) { if (noAddresses = GetHandleSize (GetLabelFieldText (objectPtr->control)) == 0) if (addresses = GetNicknameData (ab, nick, true, true)) noAddresses = !(GetHandleSize (addresses) == 1 && **addresses == ','); UnlockObject (objectPtr); } } if (tabControl && (IsTabDirty (tabControl) || noAddresses)) { TickleMe = GetControlValue(CtlViewBy)!=1; oldGroup = (*((*Aliases)[ab].theData))[nick].group; Zero (nickData); nickData.notes = NuHandle (0); nickData.theError = MemError (); // Copy any existing hidden notes into our working notes handle if (!nickData.theError) { temp2Handle = nil; if (notes = GetNicknameData (ab, nick, false, true)) if (avPairs = ParseAllAttributeValuePairs (notes, &temp2Handle, avPairHidden, avNoPairs)) { avPairPtr = LDRef (avPairs); count = GetHandleSize (avPairs) / sizeof (AttributeValueRec); for (i = 0; i < count && !nickData.theError; ++i, ++avPairPtr) { MakePStr (tag, *notes + avPairPtr->attributeOffset, avPairPtr->attributeLength); nickData.theError = AddAttributeValuePair (nickData.notes, tag, *notes + avPairPtr->valueOffset, avPairPtr->valueLength); } } ZapHandle (temp2Handle); } // Iterate all of the tab objects so we can build the new notes IterateTabObjectsUL (tabControl, ReplaceNicknameDataProc, &nickData); if (!nickData.theError) { ReplaceNicknameAddresses (ab, GetNicknameNamePStr (ab, nick, name), nickData.addresses); Tr (nickData.notes, "\015", "\003"); ReplaceNicknameNotes (ab, GetNicknameNamePStr (ab, nick, name), nickData.notes); // If the nickname's "group status" has changed, update the icon in the list if (oldGroup != (*((*Aliases)[ab].theData))[nick].group) if (LVGetItemWithNodeID (&NickList, nodeID = MakeNodeID (ab, nick), &data)) { data.iconID = ABSetIcon (ab, nick); LVSetItemWithNodeID (&NickList, nodeID, &data, true); } } if (nickData.theError) { ZapHandle (nickData.addresses); ZapHandle (nickData.notes); WarnUser (COULDNT_MOD_ALIAS, nickData.theError); } } } } Boolean ReplaceNicknameDataProc (TabObjectPtr objectPtr, NicknameDataPtr nickDataPtr) { Handle text; if (nickDataPtr->theError) return (true); // handle a field object switch (objectPtr->type) { case fieldObject: // grab the text if (text = GetLabelFieldText (objectPtr->control)) ReplaceNicknameText (objectPtr, nickDataPtr, text); break; case controlObject: case controlResourceObject: if (objectPtr->behavior) { if (((*objectPtr->behavior)->flags & behaveStringOneIsTrue) && GetControlValue (objectPtr->control)) nickDataPtr->theError = AddAttributeValuePair (nickDataPtr->notes, objectPtr->tag, &(*objectPtr->behavior)->stringOne[1], *(*objectPtr->behavior)->stringOne); if (((*objectPtr->behavior)->flags & behaveStringTwoIsFalse) && !GetControlValue (objectPtr->control)) nickDataPtr->theError = AddAttributeValuePair (nickDataPtr->notes, objectPtr->tag, &(*objectPtr->behavior)->stringTwo[1], *(*objectPtr->behavior)->stringTwo); } break; case pictureObject: // Take the URL out of the control refcon and put it in the notes ReplaceNicknameText (objectPtr, nickDataPtr, (Handle) GetControlReference (objectPtr->control)); break; } return (false); } void ReplaceNicknameText (TabObjectPtr objectPtr, NicknameDataPtr nickDataPtr, Handle text) { BinAddrHandle addresses; Str255 tag; long length; Boolean textZapNeeded; textZapNeeded = false; length = GetHandleSize (text); // could this be the expansion field? if (StringSame (objectPtr->tag, GetRString (tag, ABReservedTagsStrn + abTagEmail))) { // We need to make a copy of the text before massaging it, otherwise the text in the PETE becomes confused // for subsequent editing operations. nickDataPtr->theError = MyHandToHand (&text); if (!nickDataPtr->theError) { textZapNeeded = true; if (!GetHandleSize_ (text)) nickDataPtr->theError = PtrPlusHand_ ("\015", text, 1); if (!nickDataPtr->theError) { Tr (text, "\015", ","); nickDataPtr->addresses = text; nickDataPtr->theError = SuckAddresses (&addresses, text, false, true, false, nil); ZapHandle (addresses); textZapNeeded = false; // Note that we don't want to zap the copy of the text since it's now stored in addresses } } } // Just a plain ol' field, so the text will be slapped into an attribute value pair and put into notes else if (length) { // The 'note' field might already contain attribute value pairs, so just put this text // onto the end of our notes handle (it will be parsed correctly when we next open the Notes tab) if (StringSame (objectPtr->tag, GetRString (tag, ABReservedTagsStrn + abTagNote))) nickDataPtr->theError = HandAndHand (text, nickDataPtr->notes); else { // Massage the other email field, sorta like the regular email field if (StringSame (objectPtr->tag, GetRString (tag, ABReservedTagsStrn + abTagOtherEmail))) { nickDataPtr->theError = MyHandToHand (&text); if (!nickDataPtr->theError) { textZapNeeded = true; Tr (text, "\015", ","); nickDataPtr->theError = SuckAddresses (&addresses, text, false, true, false, nil); ZapHandle (addresses); } } if (!nickDataPtr->theError) { Tr(text,">","\002"); nickDataPtr->theError = AddAttributeValuePair (nickDataPtr->notes, objectPtr->tag, LDRef (text), length); UL (text); Tr(text,"\002",">"); } } } if (textZapNeeded) ZapHandle (text); } /************************************************************************ * AddNicknameListItems - add nicknames and address books to list ************************************************************************/ void AddNicknameListItems (ViewListPtr pView, VLNodeID nodeID) { VLNodeInfo info; OSErr theError; short addressBooks, nick, ab;; theError = noErr; GetABNick (nodeID, &ab, &nick); SetPort_ (GetMyWindowCGrafPtr (pView->wPtr)); LSetDrawingMode (false, pView->hList); // Check for the top level condition, in which case we add all the address books if (nodeID == kAdddressBookRootID) { info.isParent = true; info.useLevelZero = false; info.style = 0; info.refCon = 0; addressBooks = NAliases; for (ab = 0; ab < addressBooks && !theError; ++ab) { // Don't display plug-in nicknames if (IsPluginAddressBook (ab)) continue; #ifdef VCARD // Auto-expand personal nickname folders (i.e. my vCards) without the parent folder if (IsPersonalAddressBook (ab)) { // Don't display personal nicknames unless the user wishes to do so if (PrefIsSet (PREF_PERSONAL_NICKNAMES_NOT_VISIBLE) || !IsVCardAvailable ()) continue; // If the Personal Nicknames file is empty, create something reasonable if (!(*Aliases)[ab].theData || !GetHandleSize ((*Aliases)[ab].theData)) theError = AutoGeneratePersonalInformation (ab); if (!theError) theError = AddChildren (pView, ab); continue; } #endif // Don't display the cache file unless the user wishes to do so if (HasFeature (featureNicknameWatching) && IsHistoryAddressBook (ab) && PrefIsSet (PREF_NICK_CACHE_NOT_VISIBLE)) continue; // Add the address book itself info.iconID = (*Aliases)[ab].ro ? LOCKED_ADDRESS_BOOK_ICON : ADDRESS_BOOK_ICON; info.nodeID = MakeABNodeID (ab); info.isCollapsed = (*Aliases)[ab].collapsed; PCopy (info.name, (*Aliases)[ab].spec.name); // Add List Manager rows myself for (hopefully) better speed if (ListFlags & kfManualRowAddsExpected) (void) LAddRow (1, LVGetNextRowToAdd (), pView->hList); LVAdd (pView, &info); } } else { // Add the items for a specific parent GetABNick (nodeID, &ab, &nick); theError = AddChildren (pView, ab); } LSetDrawingMode (true, pView->hList); if (theError) WarnUser (ALIAS_DISPLAY_ERR, theError); } OSErr AddChildren (ViewListPtr pView, short ab) { VLNodeInfo info; OSErr theError; short nicknames, oldRows, count, nick, index, *sort; theError = noErr; // Add the items for a specific parent info.isParent = false; info.isCollapsed = false; info.useLevelZero = false; info.style = 0; info.refCon = 0; SortAddressBook (ab); sort = LDRef ((*Aliases)[ab].sortData); // Add List Manager rows myself for (hopefully) better speed -- but subtract out deleted rows! count = (*Aliases)[ab].theData ? (GetHandleSize_ ((*Aliases)[ab].theData) / sizeof (NickStruct)) : 0; if (ListFlags & kfManualRowAddsExpected) { nicknames = count; for (nick = 0; nick < count; ++nick) if ((*((*Aliases)[ab].theData))[nick].deleted) --nicknames; oldRows = (*pView->hList)->dataBounds.bottom; (void) LAddRow (nicknames, LVGetNextRowToAdd (), pView->hList); if ((*pView->hList)->dataBounds.bottom != oldRows + nicknames) { if ((*pView->hList)->dataBounds.bottom != oldRows) LDelRow ((*pView->hList)->dataBounds.bottom - oldRows, LVGetNextRowToAdd (), pView->hList); theError = memFullErr; } } for (index = 0; index < count && !theError; ++index) { nick = sort ? *sort++ : nick; if (!(*((*Aliases)[ab].theData))[nick].deleted) { info.nodeID = MakeNodeID (ab, nick); info.iconID = ABSetIcon (ab, nick); GetListNameBasedOnTag (SortTag, ab, nick, info.name); LVAdd (pView, &info); } } UL ((*Aliases)[ab].sortData); return (theError); } // // GetListNameBasedOnTag // // List items are formatted in specific ways, depending on the current "View By" setting: // // nickname -- Sorting by nickname // first last -- Sorting by first name // last, first -- Sorting by last name // field -- Sorting by anything else // // When sorting by anything other than nickname, we may also append the nickname at the end -- unless // we are sorting one of the special "ABDeferNickInListTags" fields or if the field value is nil. // // We further suppress nickname appending if the user has set PREF_SUPPRESS_NICKS_IN_VIEW_BY -- but, // again, only if there is indeed text available to display. // PStr GetListNameBasedOnTag (PStr tag, short ab, short nick, Str31 name) { ListNameFormatType listNameFormat; Str255 missingValue, scratch, nickname; Handle value1, value2; // Sorting by nickname if (!*tag) return (GetNicknameNamePStr (ab, nick, name)); listNameFormat = lnfOtherField; // Sorting by email is a weird thing to do, so let's make it weird here, too if (StringSame (tag, GetRString (scratch, ABReservedTagsStrn + abTagEmail))) { if (value1 = GetNicknameData (ab, nick, true, true)) MyHandToHand (&value1); } else // Sorting by some other field -- get the value value1 = GetTaggedFieldValue (ab, nick, tag); // Certain fields are combined with the key field (first/last), in which case we get another value if (StringSame (tag, GetRString (scratch, ABReservedTagsStrn + abTagFirst))) { listNameFormat = lnfFirstLast; value2 = GetTaggedFieldValue (ab, nick, GetRString (scratch, ABReservedTagsStrn + abTagLast)); } else if (StringSame (tag, GetRString (scratch, ABReservedTagsStrn + abTagLast))) { listNameFormat = lnfLastFirst; value2 = GetTaggedFieldValue (ab, nick, GetRString (scratch, ABReservedTagsStrn + abTagFirst)); } else value2 = nil; // We still might need the nickname, let's see if we do... if (!value1 || (!FindSTRNIndex (ABSuppressNickInListTagsStrn, tag) && !PrefIsSet (PREF_SUPPRESS_NICKS_IN_VIEW_BY))) GetNicknameNamePStr (ab, nick, nickname); else *nickname = 0; // // We should have everything we need (I hope), so let's crank out a name // // First, deal with the initial field value if (value1) MakePPtr (name, *value1, MIN (sizeof (Str31) - 1, GetHandleSize (value1))); else PCopy (name, GetRString (missingValue, NICKLIST_MISSING_FIELD_VALUE)); // And the secondary value if (value2) MakePPtr (scratch, *value2, GetHandleSize (value2)); else *scratch = 0; // Handle the rest depending on the format to be built switch (listNameFormat) { case lnfFirstLast: if (value2) { PMaxCatC (name, sizeof (Str31) - 1, ' '); PSCat_C (name, scratch, sizeof (Str31) - 1); } break; case lnfLastFirst: if (value2) { PSCat_C (name, "\p, ", sizeof (Str31) - 1); PSCat_C (name, scratch, sizeof (Str31) - 1); } break; case lnfOtherField: break; } // Append on the nickname (if any) if (*nickname) { GetRString (scratch, NICKLIST_PAREN); PMaxCatC (name, sizeof (Str31) - 1, ' '); if (*scratch) PMaxCatC (name, sizeof (Str31) - 1, scratch[1]); PSCat_C (name, nickname, sizeof (Str31) - 1); if (*scratch > 1) PMaxCatC (name, sizeof (Str31) - 1, scratch[2]); } ZapHandle (value1); ZapHandle (value2); return (name); } void SortAddressBook (short ab) { NickStructPtr nickPtr; Str255 scratch; long attributeOffset, attributeLength, valueOffset, valueLength; int (*compare)(); short *sortPtr, nicknames, nick; // No need to sort if nothing is dirty and we already have a sort array if (!AnyNicknamesDirty (ab) && (*Aliases)[ab].sortData && (*Aliases)[ab].theData) return; nicknames = (GetHandleSize_ ((*Aliases)[ab].theData) / sizeof (NickStruct)); // Allocate some space for our sort array ZapHandle ((*Aliases)[ab].sortData); (*Aliases)[ab].sortData = NuHandle ((nicknames+1) * sizeof (short)); if (!(*Aliases)[ab].sortData) return; GetSortTag (); // Fill the array with one-to-one indexes to the items in the address book and setup the sort data in our toc LDRef (Aliases); sortPtr = LDRef ((*Aliases)[ab].sortData); nickPtr = LDRef ((*Aliases)[ab].theData); for (nick = 0; nick < nicknames; ++nick) { if (*SortTag) { if (FindTaggedFieldValueOffsets (ab, nick, SortTag, &attributeOffset, &attributeLength, &valueOffset, &valueLength)) { nickPtr->valueOffset = valueOffset; nickPtr->valueLength = valueLength; } else { nickPtr->valueOffset = -1; nickPtr->valueLength = -1; } ++nickPtr; } *sortPtr++ = nick; } *sortPtr = -1; // Quicksort needs a sentinel at the end UL ((*Aliases)[ab].theData); UL ((*Aliases)[ab].sortData); UL (Aliases); // If there is no tag, then we sort by nickname if (!*SortTag) compare = NickCompare; else compare = StringSame (SortTag, GetRString (scratch, ABReservedTagsStrn + abTagEmail)) ? EmailCompare : FieldCompare; gSortAB = ab; LDRef ((*Aliases)[ab].sortData); QuickSort (*(*Aliases)[ab].sortData, sizeof (short), 0, nicknames - 1, (void*) compare,(void*) NickSwap); UL ((*Aliases)[ab].sortData); } // // GetSortTag // // Make the sort tag by playing around with the current View By menu selection // void GetSortTag (void) { TabObjectPtr objectPtr; Str255 shortName; // Figure out how the user prefers sorting *SortTag = 0; GetMenuItemText (PopUpMenuH (CtlViewBy), GetControlValue (CtlViewBy), shortName); shortName[++shortName[0]] = ':'; if (objectPtr = FindObjectWithShortName (Tabs, shortName)) { PCopy (SortTag, objectPtr->tag); UnlockObject (objectPtr); } } /************************************************************************ * NickCompare - compare two nicknames ************************************************************************/ int NickCompare (short *n1, short *n2) { Str255 name1,name2; PStr s1,s2; // The sentinel might get passed into the sort routine. If so, return as needed if (*n1 == -1) return (*n2 == -1 ? 0 : 1); if (*n2 == -1) return (-1); s1 = GetNicknameNamePStr(gSortAB, *n1, name1); s2 = GetNicknameNamePStr(gSortAB, *n2, name2); // The following doesn't work. 's1' and 's2' should always have some value // since GetNicknameNamePStr returns 'name1' or 'name2'. Instead, let's // just let StringCompare do its thing // if (s1) return s2 ? StringComp(s1,s2) : -1; // else return s2 ? 1 : 0; return (StringComp(s1,s2)); } /************************************************************************ * NickSwap - swap two nicknames ************************************************************************/ void NickSwap (short *n1, short *n2) { short temp = *n2; *n2 = *n1; *n1 = temp; } /************************************************************************ * FieldCompare - compare two nicknames using tagged fields as the data ************************************************************************/ int FieldCompare (short *n1, short *n2) { NickStructPtr nickPtr1, nickPtr2; Handle notes1, notes2; // The sentinel might get passed into the sort routine. If so, return as needed if (*n1 == -1) return (*n2 == -1 ? 0 : 1); if (*n2 == -1) return (-1); notes1 = GetNicknameData (gSortAB, *n1, false, true); notes2 = GetNicknameData (gSortAB, *n2, false, true); nickPtr1 = &(*((*Aliases)[gSortAB].theData))[*n1]; nickPtr2 = &(*((*Aliases)[gSortAB].theData))[*n2]; if (notes1 && nickPtr1->valueOffset >= 0) { if (notes2 && nickPtr2->valueOffset >= 0) return (strincmp (*notes1 + nickPtr1->valueOffset, *notes2 + nickPtr2->valueOffset, MIN (nickPtr1->valueLength, nickPtr2->valueLength))); else return (-1); } else { if (notes2 && nickPtr2->valueOffset >= 0) return (1); else { return NickCompare (n1, n2); } } } /************************************************************************ * EmailCompare - compare two nicknames using email addresses as the data ************************************************************************/ int EmailCompare (short *n1, short *n2) { Handle value1, value2; Size value1Size, value2Size; // The sentinel might get passed into the sort routine. If so, return as needed if (*n1 == -1) return (*n2 == -1 ? 0 : 1); if (*n2 == -1) return (-1); if (value1 = GetNicknameData (gSortAB, *n1, true, true)) value1Size = GetHandleSize (value1); if (value2 = GetNicknameData (gSortAB, *n2, true, true)) value2Size = GetHandleSize (value2); if (value1) { if (value2) return (strincmp (*value1, *value2, MIN (value1Size, value2Size))); else return (-1); } else { if (value2 && value2Size) return (1); else return NickCompare (n1, n2); } } void PositionPushButtons (MyWindowPtr win, short hTab1, short hTab2, short vTab5) { short tabWidth, buttonHeight, buttonWidth; short iChatButtonWidth; ButtonFit(CtlChat); iChatButtonWidth = ControlWi(CtlChat) - 4*INSET; // darn giant aqua button can bite me tabWidth = hTab2 - hTab1; buttonHeight = ControlHi (CtlCc); buttonWidth = (tabWidth - 2 * INSET - 2 * INSET - kHorzZoomWidth) / 4; if (buttonWidth < iChatButtonWidth) buttonWidth = (tabWidth - 2 * INSET - 2 * INSET - kHorzZoomWidth - iChatButtonWidth) / 3; else iChatButtonWidth = buttonWidth; // horizontal zoom button MoveMyCntl (CtlZoomHorizontal, hTab2 - kHorzZoomWidth, vTab5 + (buttonHeight - kHorzZoomWidth) / 2, kHorzZoomWidth, kHorzZoomWidth); // To, Cc, Bcc, iChat MoveMyCntl (CtlTo, hTab1, vTab5, buttonWidth, buttonHeight); MoveMyCntl (CtlCc, hTab1 + buttonWidth + INSET, vTab5, buttonWidth, buttonHeight); MoveMyCntl (CtlBcc, hTab1 + 2 * (buttonWidth + INSET), vTab5, buttonWidth, buttonHeight); MoveMyCntl (CtlChat, hTab1 + 3 * (buttonWidth + INSET), vTab5, iChatButtonWidth, buttonHeight); } void BuildViewByMenu (ControlHandle tabControl, ControlHandle viewByControl) { MenuHandle viewByMenu; Str255 tag, viewByString, title; short index; if (tabControl && viewByControl) if (viewByMenu = PopUpMenuH (viewByControl)) { SetControlValue (viewByControl, 1); // The current view by label GetPref (viewByString, PREF_NICK_VIEW_BY); index = 1; do { GetRString (tag, ABViewByTagsStrn + index++); if (*tag) if (tag[1] == '-') { SetControlMaximum (viewByControl, GetControlMaximum (viewByControl) + 1); AppendMenu (viewByMenu, "\p(-"); } else { FindShortNameWithTag (tabControl, tag, title); if (*title) { SetControlMaximum (viewByControl, GetControlMaximum (viewByControl) + 1); // Remove the ':' following the field label if (title[title[0]] == ':') --title[0]; MyAppendMenu (viewByMenu, title); if (StringSame (viewByString, title)) SetControlValue (viewByControl, index); } } } while (*tag); } } /************************************************************************ * AddressBookLVCallBack - callback function for list View ************************************************************************/ long AddressBookLVCallBack (ViewListPtr pView, VLCallbackMessage message, long data) { VLNodeInfo *pInfo; long fResults; short ab, nick; fResults = 0; switch (message) { case kLVAddNodeItems: AddNicknameListItems (pView, (VLNodeID) data); break; case kLVGetParentID: fResults = ((VLNodeInfo *) data)->nodeID; break; case kLVOpenItem: ABMessageTo (TO_HEAD, CurrentModifiers ()); break; case kLVMoveItem: case kLVCopyItem: ABMove ((VLNodeInfo*) data, message == kLVCopyItem); break; case kLVDeleteItem: // For now we won't respond to requests to delete nicknames via the Delete key. Eventually we // need to support a protected method for deleting listview items that probably involves command-Delete // or something. // // Ah! Except, this is the mechanism we use to remove items from the list view when the user has dragged an item // to the trash. What to do, what to do... Why, overload the 'data' parameter, of course. // if (data == dataDeleteFromDrag || (data == dataDeleteFromKeyboard && PrefIsSet (PREF_REMOVE_NICKS_WITH_DELETE_KEY))) ABRemove (); break; case kLVRenameItem: DoRenameCallback ((StringPtr) data); break; case kLVGetFlags: fResults = ListFlags; break; case kLVQueryItem: pInfo = (VLNodeInfo *)data; switch (pInfo->query) { case kQuerySelect: fResults = true; break; case kQueryDrag: fResults = true; break; case kQueryDrop: fResults = false; GetABNick (pInfo->nodeID, &ab, &nick); if (ValidAddressBook (ab) && !(*Aliases)[ab].ro) { fResults = !ValidNickname (nick); if (ValidNickname (nick) && (*((*Aliases)[ab].theData))[nick].group && PrefIsSet (PREF_NICK_DROP_ON_GROUPS)) fResults = true; } break; case kQueryDropParent: GetABNick (pInfo->nodeID, &ab, &nick); fResults = ValidNickname (nick) && ValidAddressBook (ab) && !(*Aliases)[ab].ro; break; case kQueryDragExpand: fResults = PrefIsSet (PREF_NICK_EXPAND_DRAGS); break; case kQueryRename: // Only allow in list renames when the current view is By Nickname and the user pref is set -- or if the node is an address book GetABNick (pInfo->nodeID, &ab, &nick); if (*SortTag || (!PrefIsSet (PREF_ALLOW_NICK_RENAME_IN_LIST) && ValidNickname (nick))) fResults = false; else { if (ab >= 0 && nick == kNickUndefined) fResults = (!((*Aliases)[ab].ro || IsEudoraAddressBook (ab) || IsHistoryAddressBook (ab))); else fResults = !(*Aliases)[ab].ro; } break; case kQueryDCOpens: fResults = !pInfo->isParent; break; } break; case kLVAddDragItem: fResults = ABAddDragFlavors (data); break; case kLVSendDragData: fResults = ABDoSendDragData (data); break; case kLVExpandCollapseItem: pInfo = (VLNodeInfo *)data; (*Aliases)[GetAddressBook (pInfo->nodeID)].collapsed = pInfo->isCollapsed; break; case kLVSelectItem: ABSelectNickname ((VLNodeID) data); break; case kLVUnselectItem: ABUnselectNickname ((VLNodeID) data); break; } return (fResults); } void DoRenameCallback (PStr newName) { VLNodeInfo data; short ab, nick; if (LVGetItem (&NickList, 1, &data, true)) { GetABNick (data.nodeID, &ab, &nick); ABRename (ab, nick, newName); } } Boolean RemoveAddressBook (short ab) { CellRec *pCellData; VLNodeInfo data; Cell c; Str31 name; OSErr theError; short numAdressBooks, abIndex, nick, count, item, offset, len; if (ComposeStdAlert (Stop, ALIAS_REMOVE_FILE_ALERT, (*Aliases)[ab].spec.name) != 1) return (false); // With this address book going away, we should also remove any nicknames in the list from the recipient list // (This code was formerly misplaced _after_ the nickname file had been zapped... BOOM!) count = (*Aliases)[ab].theData ? (GetHandleSize_ ((*Aliases)[ab].theData) / sizeof (NickStruct)) : 0; for (nick = 0; nick < count; nick++) { GetNicknameNamePStr (ab, nick, name); if (IsNicknameOnRecipList (ab, nick)) RemoveFromTo (name); TBRemoveDefunctNicknameButton (name); } // De-allocate underlying structures ZapAliasFile (ab); // Move file to trash LDRef (Aliases); theError = MyFSpMoveToTrash (&(*Aliases)[ab].spec); if (theError) theError = FSpDelete (&(*Aliases)[ab].spec); UL (Aliases); if (!theError) { // Get rid of the item in our list if (LVGetItemWithNodeID (&NickList, MakeABNodeID (ab), &data)) { item = data.rowNum; // This is going to get slightly nasty... We're going to run through all the remaining items that // follow in the list view and reset all of the nodeID's to reflect an "adjusted" address book // index. It is important that we do this since the remaining list entries now all point to one // address book beyond... c.h = 0; for (--item; item < (*NickList.hList)->dataBounds.bottom; item++) { c.v = item; LGetCellDataLocation (&offset, &len, c, NickList.hList); if (pCellData = len > 0 ? (CellRec *) (*(*NickList.hList)->cells + offset) : nil) pCellData->nodeID = MakeNodeID (GetAddressBook (pCellData->nodeID) - 1, GetNickname (pCellData->nodeID)); } } // Shift around the data in the big hangin' nickname structure numAdressBooks = NAliases; if (ab != numAdressBooks - 1) for (abIndex = ab + 1; abIndex < numAdressBooks; abIndex++) (*Aliases)[abIndex - 1] = (*Aliases)[abIndex]; SetHandleBig_ (Aliases, (numAdressBooks - 1) * sizeof (AliasDesc)); // We may have to move the location of the History List file, too // No longer needed since we mark the history list file // if (HasFeature (featureNicknameWatching)) // if (gEudoraNicknamesCacheFileIndex >= 0) // if (ab == gEudoraNicknamesCacheFileIndex) // gEudoraNicknamesCacheFileIndex = kAddressBookUndefined; // else // if (ab < gEudoraNicknamesCacheFileIndex) // --gEudoraNicknamesCacheFileIndex; return (true); } else WarnUser (ALIAS_REMOVE_NICK_FILE_ERR, theError); return (false); } // // RemoveNickname // // The logic within this function is sort of backwards from what Boolean RemoveNickname (short ab, short nick) { VLNodeInfo data; Boolean result; if (result = (ab >= 0 && !(*Aliases)[ab].ro && nick >= 0)) // Just mark it deleted if (result = LVGetItemWithNodeID (&NickList, MakeNodeID (ab, nick), &data)) { (*((*Aliases)[ab].theData))[nick].addressesDirty = true; (*((*Aliases)[ab].theData))[nick].deleted = true; } return (result); } // // ParseAttributeValue // // Parses a range of text for an attribute/value pair, returning a pointer // to the character following the pair (the character after the '>'), or 'nil' // if no pair was parsed. // // Tagged fields have the absolute form: // // // // This function does not make any attempt to validate either the tag or // the value Ñ all it cares about is finding brackets and colons, making // no attempt to handle nesting or escaping or anything else. Therefore, // tags can contain breaking spaces and other heinous characters. // // The algorithm goes something like this: // 1. Scan until we find a '<' and save this spot // 2. Scan until we find a ':' and save this one, too. // 3. Scan until we find a '>' and save this spot once again // // IMPORTANT NOTE: // // It is also important to note that the pointers returned in 'attribute' and 'value' are // generally pointers into the notes handle itself. If you dereference notes and call // this routine to parse the data, make sure the data actually has a staic place to live!! // // Much safer is ParseAttributeValuePairHandle, which returns allocates its own destination // storage, returning the data in a pair of Handles. // UPtr ParseAttributeValuePair (UPtr start, long length, UPtr *attribute, long *attributeLength, UPtr *value, long *valueLength) { UPtr end, spot; short bc; // bracket count Boolean found; found = false; bc = 0; spot = start; end = start + length - 1; do { if (*spot == kFieldTagBegin) { ++bc; *attribute = ++spot; do { if (*spot == kFieldTagSeparator) { *attributeLength = spot - *attribute; *value = ++spot; do { if (*spot == kFieldTagEnd) if (--bc == 0) { *valueLength = spot - *value; found = true; } } while (!found && ++spot <= end); } } while (!found && ++spot <= end); } } while (!found && ++spot <= end); return (found ? spot + 1 : nil); } // // ParseAttributeValuePairHandle // // ParseAttributeValuePair, except putting the output into handles we allocate. Don't forget // to dispose of the results when you're done. // // It's also okay to pass in nil for either the attribute or value if we don't care about // those fields. // UPtr ParseAttributeValuePairHandle (UPtr start, long length, Handle *hAttribute, Handle *hValue) { UPtr attribute, value, spot; OSErr theError; long attributeLength, valueLength; if (hAttribute) *hAttribute = nil; if (hValue) *hValue = nil; if (spot = ParseAttributeValuePair (start, length, &attribute, &attributeLength, &value, &valueLength)) { theError = noErr; if (hAttribute) theError = PtrToHand (attribute, hAttribute, attributeLength); if (!theError && hValue) theError = PtrToHand (value, hValue, valueLength); if (theError) { if (hAttribute) ZapHandle (*hAttribute); if (hValue) ZapHandle (*hValue); } } return (spot); } // // ParseAttributeValuePairStr // // ParseAttributeValuePair, except putting the output into pascal strings // // It's also okay to pass in nil for either the attribute or value if we don't care about // those fields. // UPtr ParseAttributeValuePairStr (UPtr start, long length, PStr attributeStr, PStr valueStr) { UPtr attribute, value, spot; long attributeLength, valueLength; if (attributeStr) *attributeStr = nil; if (valueStr) *valueStr = nil; if (spot = ParseAttributeValuePair (start, length, &attribute, &attributeLength, &value, &valueLength)) { if (attributeStr) MakePPtr (attributeStr, attribute, attributeLength); if (valueStr) MakePPtr (valueStr, value, valueLength); } return (spot); } // // AddAttributeValuePair // // Create an attribute value pair and append it onto a notes handle. // OSErr AddAttributeValuePair (Handle notes, PStr attribute, Ptr value, long length) { OSErr theError; UPtr notesPtr; Size notesSize; theError = noErr; // Grow the notes field to accomodate a structured pair notesSize = GetHandleSize (notes); SetHandleBig (notes, notesSize + attribute[0] + length + 3); theError = MemError (); if (!theError) { notesPtr = *notes + notesSize; *notesPtr++ = kFieldTagBegin; BlockMoveData (&attribute[1], notesPtr, attribute[0]); notesPtr += attribute[0]; *notesPtr++ = kFieldTagSeparator; BlockMoveData (value, notesPtr, length); notesPtr += length; *notesPtr++ = kFieldTagEnd; } return (theError); } OSErr AppendLeftoverText (AccuPtr a, UPtr from, UPtr to) { OSErr theError; theError = noErr; if (from <= to) { while (IsSpace(*from)) // skip leading white space ++from; if (from <= to) { if (a->offset) theError = AccuAddChar (a, '\015'); if (!theError) theError = AccuAddPtr (a, from, to - from + 1); } } return (theError); } // // ParseAllAttributeValuePairs // // Parses a text handle for all attribute/value pairs, returning a handle to // a structure containing the offset and lengths for each. We also create // and return in 'leftovers' a handle to any text that was _not_ parsed as // an attribute value pair. // AttributeValueHandle ParseAllAttributeValuePairs (Handle notes, Handle *leftovers, long requestedTypes, long unrequestedTypesToBePutInLeftovers) { TabFieldHandle fields; AttributeValueHandle avPairs; AttributeValueRec av; Accumulator leftoversAccumulator; OSErr theError; Str31 tag; UPtr notesPtr, newPtr, attribute, value; Size notesSize; avPairs = nil; fields = nil; theError = AccuInit (&leftoversAccumulator); if (!theError) { avPairs = NuHandle (0); theError = MemError (); } if (!theError) fields = Tabs && AB.inited ? GetTabFieldStrings (Tabs, tabTagName) : GetTabFieldsFromResources (tabTagName, CREATOR, kNickTabType); // Walk through the 'notes', looking for attribute/value pairs. if (notes && avPairs && fields) { notesSize = GetHandleSize (notes); notesPtr = LDRef (notes); // Go fish... while (!theError && (newPtr = ParseAttributeValuePair (notesPtr, notesSize - (notesPtr - *notes), &attribute, &av.attributeLength, &value, &av.valueLength))) { // I got one! av.attributeOffset = attribute - *notes; av.valueOffset = value - *notes; // If anything appears between this tag and the previous tag, put it in leftovers if (!theError) theError = AppendLeftoverText (&leftoversAccumulator, notesPtr, attribute - 2); // Make the attribute tag MakePStr (tag, attribute, av.attributeLength); // What type of tag did we find? av.type = 0; if (FindSTRNIndex (ABHiddenTagsStrn, tag)) av.type |= avPairHidden; if (FindSTRNIndex (ABUnseachableTagsStrn, tag)) av.type = avPairUnsearchable; if (!av.type) av.type = FindFieldString (fields, tag) ? avPairKnown : avPairUnknown; // Add only requested pairs to our list if (av.type & requestedTypes) theError = PtrPlusHand (&av, avPairs, sizeof (av)); else // And if we've been asked, save unrequested types in leftovers (otherwise, they are ignored completely) if (av.type & unrequestedTypesToBePutInLeftovers) { theError = AccuAddChar (&leftoversAccumulator, '\015'); if (!theError) theError = AccuAddPtr (&leftoversAccumulator, attribute - 1, value - attribute + av.valueLength + 2); } notesPtr = newPtr; } // Any leftovers at the end of the note? if (!theError) theError = AppendLeftoverText (&leftoversAccumulator, notesPtr, *notes + notesSize - 1); UL (notes); } // Clean up if (!theError) { AccuTrim (&leftoversAccumulator); *leftovers = leftoversAccumulator.data; leftoversAccumulator.data = nil; } else ZapHandle (avPairs); ZapHandle (fields); AccuZap (leftoversAccumulator); return (avPairs); } short ABSetIcon (short ab, short nick) { Boolean isRecip; #ifdef VCARD if (IsPersonalAddressBook (ab) && IsVCardAvailable ()) return (PERSONAL_NICKNAME_ICON); #endif isRecip = IsNicknameOnRecipList (ab, nick); if ((*((*Aliases)[ab].theData))[nick].group) return (isRecip ? RECIPIENT_GROUP_ICON : GROUP_ICON); return (isRecip ? RECIPIENT_NICKNAME_ICON : NICKNAME_ICON); } OSErr GetPhotoSpec (FSSpec *spec, short ab, short nick, Boolean *alreadyExists) { FSSpec nickFolder, photoFolder, abPhotoFolder; Str255 photoFolderName, nickname; OSErr theError; long dirID; *alreadyExists = false; // Find (or create) the Nicknames Folder if (theError = SubFolderSpec (NICK_FOLDER, &nickFolder)) { SimpleMakeFSSpec (Root.vRef, Root.dirId, GetRString (nickFolder.name, NICK_FOLDER), &nickFolder); theError = FSpDirCreate (&nickFolder, smSystemScript, &dirID); } if (!theError) { IsAlias (&nickFolder, &nickFolder); theError = FSMakeFSSpec (nickFolder.vRefNum, SpecDirId (&nickFolder), GetRString (photoFolderName, PHOTO_FOLDER), &photoFolder); } // Create the Photo Album Folder if (theError == fnfErr) theError = FSpDirCreate (&photoFolder, smSystemScript, &dirID); if (!theError) { IsAlias (&photoFolder, &photoFolder); // Create a folder inside the Photo Album for the current address book theError = FSMakeFSSpec (photoFolder.vRefNum, SpecDirId (&photoFolder), (*Aliases)[ab].spec.name, &abPhotoFolder); if (theError == fnfErr) theError = FSpDirCreate (&abPhotoFolder, smSystemScript, &dirID); } // Make an FSSpec for the nickname itself if (!theError) { IsAlias (&abPhotoFolder, &abPhotoFolder); theError = FSMakeFSSpec (abPhotoFolder.vRefNum, SpecDirId (&abPhotoFolder), GetNicknameNamePStr (ab, nick, nickname), spec); if (theError == fnfErr) theError = noErr; else { *alreadyExists = true; IsAlias (spec, spec); } } return (theError); } // // Old scary crufty crap appears below... (not all that related to the address book window) // /************************************************************************ * NewNick - create a new alias for the user ************************************************************************/ #ifdef VCARD void NewNick(Handle addresses,FSSpec *vcardSpec,short which) { AskNickname (addresses, vcardSpec, which); // Wow... this routine sure did get stripped down... } #else void NewNick(Handle addresses,short which) { AskNickname (addresses, which); // Wow... this routine sure did get stripped down... } #endif /************************************************************************ * NewNickLow - Acually create a new alias for the user ************************************************************************/ short NewNickLow(Handle addresses,Handle notes,short which,Str63 name,Boolean makeRecip,NickReplaceEnum nre,Boolean doSave) { return AddNickname(addresses,notes,which,name,makeRecip,nre,doSave); } /************************************************************************ * AddNickname - Create a new alias for the user * * Note!! No doubt there are still a lot of Gruby-ized conditions in this routine, but -- in general -- * The 'addresses' and 'notes' passed to this routine are COPIED into new handles which are then * placed into the TOC. Therefore, it is safe for calling routines to zap the handles they pass. * ************************************************************************/ short AddNickname(Handle addresses,Handle notes,short which,Str63 name,Boolean makeRecip,NickReplaceEnum nre,Boolean doSave) { short index; long addrSize; Handle tempHandle = nil,temp2Handle = nil; long totalNicks; short realCount,i; short nickCollision; Boolean replacing = false; Byte comma = ','; OSErr err = noErr; Boolean notesExist; RegenerateAllAliases(false); if ((*Aliases)[which].ro) which = FindAddressBookType (eudoraAddressBook); totalNicks = (*Aliases)[which].theData ? (GetHandleSize_((*Aliases)[which].theData)/sizeof(NickStruct)) : 0; realCount = 0; for (i=0;i= 0) { if (nre == nrDifferent) { nickCollision = NickRepAlert(name); // If it still thinks it is different, cancel was really hit if (nickCollision == nrDifferent) return(-1); } else nickCollision = (short) nre; // 2/2/01 (jp) ¥ If we are replacing or adding to an existing nickname that is currently selected, unselect before changing anything. if (AB.inited) if (which == AB.abDisplayed && index == AB.nickDisplayed && (nickCollision == nrReplace || nickCollision == nrAdd)) { Str31 massagedName; GetListNameBasedOnTag (SortTag, which, index, massagedName); LVUnselect (&NickList, MakeNodeID (which, index), massagedName, true); } switch (nickCollision) { case nrDifferent: #ifdef VCARD nre = AskNickname(nil, nil, which); #else nre = AskNickname(nil, which); #endif if (nre == nrCancel) return(-1); break; case nrReplace: replacing = true; break; case nrAdd: replacing = true; // Get addresses // Append addresses tempHandle = GetNicknameData(which,index,true,true); if (tempHandle) { temp2Handle = NuHandle(0); if (!temp2Handle) {WarnUser(ALIAS_NEW_NICK_ERR,MemError());return(-1);} if (err = HandPlusHand(tempHandle,temp2Handle)) break; if (err=PtrPlusHand(&comma,temp2Handle,1)) break; if (err=HandPlusHand(addresses,temp2Handle)) break; addresses = temp2Handle; temp2Handle = nil; } if (notes) ZapHandle(notes); // Get notes // Copy notes temp2Handle = GetNicknameData(which,index,false,true); if (temp2Handle) { notes = NuHandle(0); if (!notes) {WarnUser(ALIAS_NEW_NICK_ERR,MemError());return(-1);} if (err = HandPlusHand(temp2Handle,notes)) break; } break; } } if (addresses) NickUniq(addresses,"\p,",True); if (makeRecip) AddStringAsTo(name); SetAliasDirty(which); // Both of these routines will make a copy of the addresses if (replacing) ReplaceNicknameAddresses(which,name,addresses); else AddNickToTOCfromName(which,name,addresses); // When adding a new nickname, we'll add the 'added' change bit to the notes notesExist = notes ? true : false; if (!replacing && PrefIsSet (PREF_CHANGE_BITS_FOR_CONDUIT)) { if (!notes) notes = NuHandle (0); if (notes) { Str255 scratch, idTag; SetNicknameChangeBit (notes, changeBitAdded, true); // Generate a unique ID for this nickname and add it to the notes NumToString (NickGenerateUniqueID(), scratch); SetTaggedFieldValueInNotes (notes, GetRString (idTag, ABHiddenTagsStrn + abTagUniqueID), &scratch[1], *scratch, nickFieldReplaceExisting, 0, nil); } } // This routine will make a copy of the notes if (notes) ReplaceNicknameNotes(which,name,notes); if (!notesExist) ZapHandle (notes); if (doSave) SaveAliases(true); return (index >= 0 ? index : NickMatchFound((*Aliases)[which].theData,NickHash(name),name,which)); } /************************************************************************ * CanMakeNick - should we enable the "make nickname" item? ************************************************************************/ Boolean CanMakeNick(void) { return(AB.inited&&((LVCountSelection(&NickList)>1&&FrontWindow_()==GetMyWindowWindowPtr(Win))||HasNickScanCapability (Win->pte)) || PhCanMakeNick()); } Boolean NickUserFieldExists(PStr theField) { short numberOfUserFields; short i; Str31 tempField; fieldInfo **userFieldsInfo; OSErr err = noErr; short reqFields,optFields; long height,width; Str255 scratch; Str31 fieldName; /* * Determine number of extra fields */ #ifdef ONE reqFields = 1; optFields = 0; #else reqFields = CountStrn(NickManLabelStrn); optFields = CountStrn(NickOptLabelStrn); #endif numberOfUserFields = reqFields + optFields; userFieldsInfo = NuHandle(sizeof(fieldInfo) * (reqFields + optFields)); if (!userFieldsInfo) { err = MemError(); WarnUser(MEM_ERR,err); return (err); } for (i=0;i= 0) tagPtr = LDRef(theNotes) + offset; if (tagPtr) dataPtr = tagPtr + *theField + 1; if ( dataPtr && *dataPtr == ':' ) dataPtr++; if (dataPtr) endPtr = strchr(dataPtr,'>'); length = endPtr - dataPtr; for (count = 0;count'); } } else returnValue = true; if ((tagPtr && dataPtr && endPtr) || doingDrag) { // Copy from end of tag to end of data if (doingDrag) { if (theNotes == nil || *theNotes == nil || tagPtr == nil) { returnValue = true; UL(theNotes); *nickNameEmpty = true; return (noErr); } length = endPtr - dataPtr; if (length > 63) length = 63; BlockMoveData(dataPtr,sViewData+1,length); *sViewData = length; UL(theNotes); return(noErr); } else { length = endPtr - dataPtr; MakePStr(tempStr,dataPtr,length); // if field is not name, add nickname if (!EqualStrRes(theField,NickManKeyStrn+1) && !doingAE) { GetNicknameNamePStr(which,nickNum,anotherTemp); ComposeRString(scratch,NICK_VIEW_ANNOTATE,tempStr,anotherTemp); PSCopy(tempStr,scratch); } } } else if (!doingDrag && !doingAE) { PCopy(tempStr,"\pÇ"); // PCat(tempStr,*((*((*Aliases)[which].theData))[nickNum].theName)); GetNicknameNamePStr(which,nickNum,anotherTemp); PSCat(tempStr,anotherTemp); PSCatC(tempStr,'È'); returnValue = true; } // (jp) 12-19-99 When we're getting field data from an AppleEvent we're going to // replace the ETX characters with CR's, otherwise replace with spaces // as before for (i=1;i<=*tempStr;i++) // Strip out illegal characters if (tempStr[i]=='\003') tempStr[i] = doingAE ? '\015' : ' '; PCopy(sViewData,tempStr); if (theNotes) UL(theNotes); *nickNameEmpty = returnValue; return (noErr); } /************************************************************************ * Makes a new nickname file placing it in the correct spot in the nickname list ************************************************************************/ short NickMakeNewFile (Str63 name) { AliasDesc ad; FSSpec folderSpec; OSErr theError; long dirID; short ab, i, totalAB; Str63 abName; // Does it already exist? for (ab = NAliases-1; ab>=0; ab--) { PCopy(abName,(*Aliases)[ab].spec.name); if (StringSame(abName,name)) return ab; } ab = kAddressBookUndefined; // Create an address book record Zero (ad); ad.type = regularAddressBook; ad.hNames = NuHandle (0); ad.theData = NuHandle (0); theError = MemError (); if (!theError) { HNoPurge (ad.theData); theError = MemError (); } // Create the nickname file if (!theError) { if (!SubFolderSpec (NICK_FOLDER, &folderSpec)) SimpleMakeFSSpec (folderSpec.vRefNum, folderSpec.parID, name, &ad.spec); else { // If not, create one and then create our file spec SimpleMakeFSSpec (Root.vRef, Root.dirId, GetRString (folderSpec.name, NICK_FOLDER),&folderSpec); theError = FSpDirCreate (&folderSpec, smSystemScript, &dirID); if (!theError) { SubFolderSpec (NICK_FOLDER, &folderSpec); SimpleMakeFSSpec (folderSpec.vRefNum, folderSpec.parID, name, &ad.spec); } } if (!theError) theError = FSpCreate (&ad.spec, CREATOR, MAILBOX_TYPE, smSystemScript); if (theError) FileSystemError (SAVE_ALIAS, ad.spec.name, theError); } // Find its position in the nickname file list. // This position should be after the Personal, Eudora and History // address books, but before the Plugin address books: // Personal Nicknames // Eudora Nicknames // Regular Nickname Files // History List // Plugin Nickname Files if (!theError) { totalAB = NAliases; i = 0; #ifdef VCARD // Skip the personal address book if (IsPersonalAddressBook (i)) ++i; #endif // Skip the eudora address book if (IsEudoraAddressBook (i)) ++i; // Skip to the end of the regular address books while (i < totalAB && IsRegularAddressBook (i)) ++i; // 'i' should now be pointing to where in the list we wish to insert the nick file record Munger (Aliases, i * sizeof (AliasDesc), nil, 0, &ad, sizeof (AliasDesc)); theError = MemError (); if (theError) WarnUser (ALIAS_NEW_NICK_FILE_ERR, theError); else { ab = i; ABTickleHardEnoughToMakeYouPuke(); } } // Clean up... if (theError) { ZapHandle (ad.hNames); ZapHandle (ad.theData); } return (ab); } /************************************************************************ * AERemoveNick - get rid of a nickname via AppleEvents ************************************************************************/ Boolean AERemoveNick(short index,short which) { Str63 name; short numOfAliasFiles = NAliases; OSErr theErr = noErr; if (index < 0) // Can't remove a nickname file { #ifdef VCARD if (IsEudoraAddressBook(which) || IsPersonalAddressBook(which)) #else if (IsEudoraAddressBook(which)) #endif return (false); // De-allocate underlying structures ZapAliasFile(which); // Move file to trash theErr = MyFSpMoveToTrash(&(*Aliases)[which].spec); if (theErr) FSpDelete(&(*Aliases)[which].spec); return (true); } // PCopy(name,*((*((*Aliases)[which].theData))[index].theName)); GetNicknameNamePStr(which,index,name); if (IsNicknameOnRecipList(which,index)) RemoveFromTo(name); RemoveNamedNickname(which,name); TBRemoveDefunctNicknameButton (name); SetAliasDirty(which); RemoveNickFromAddressBookList (which, index, true); return (true); } /************************************************************************ * NickWinIsOpen - is the Address Book window open? ************************************************************************/ Boolean NickWinIsOpen(void) { return AB.inited && GetWindowKind(GetMyWindowWindowPtr(Win)) == ALIAS_WIN && IsWindowVisible(GetMyWindowWindowPtr(Win)); } /************************************************************************ * AliasWinIsOpen - is the alias window open? ************************************************************************/ Boolean AliasWinIsOpen(void) { return(AB.inited!=nil); } void RemoveNickFromAddressBookList (short which, short index, Boolean doSave) { if (AB.inited) RemoveNickname (which, index); else if (doSave) SaveAliases(true); } /********************************************************************** * AliasWinRefresh - refresh the current nickname **********************************************************************/ void AliasWinRefresh(void) { } // // VanquishRecipientList // // This code used to be in Alias close. I put it here instead and // rearranged things a little // void VanquishRecipientList (Boolean discardChanges) { if (OldRecipientMenu) { if (discardChanges) { // Need to restore recipient list SetupRecipMenusWith(OldRecipientMenu); ToMenusChanged(); } // Dispose of copy of recipient list ZapHandle (OldRecipientMenu); } } // // SaveExpandedAddressBookNames // // This code used to be in Alias close. I put it here instead and // rearranged things a little. We could make this routine a lot // simpler by just storing a state boolean in the resource fork of // the address book itself -- but that would be trouble for anyone // planning to store their address book files on a shared server. // // Also, there used to be logic to default empty address books to // closed, even if the user left the address book open. Seems // valid for the user to choose to do this, so I'm axing this code. // // We also used to do a big handle compare to see if the list had // changed. Honestly, though, these days of faster CPUs and fast // disk access makes this check unnecessary -- so we'll just copy // out the list regardless. // void SaveExpandedAddressBookNames (void) { Handle dataH; OSErr theError; short numABs, ab, count; count = 0; numABs = NAliases; // Make space for the count, which we store in a short dataH = NuHandle (sizeof (short)); theError = MemError (); for (ab = 0; !theError && ab < numABs; ab++) if ((*Aliases)[ab].collapsed) { ++count; theError = PtrPlusHand_ ((*Aliases)[ab].spec.name, dataH, *(*Aliases)[ab].spec.name + 1); } if (!theError) { *(short*)(*dataH) = count; ZapSettingsResource ('STR#', NickFileCollapseStrn); AddMyResource_ (dataH,'STR#', NickFileCollapseStrn, ""); theError = ResError (); } if (!theError) MyUpdateResFile (SettingsRefN); if (theError) ZapHandle (dataH); } /************************************************************************ * NickRepAlert - nickname replacement alert, with vetted cancel return ************************************************************************/ short NickRepAlert(PStr candidate) { short err = AlertStr(NICK_REP_ALRT,Note,candidate); if (err==userCanceledErr || err==CANCEL_ITEM) err = nrDifferent; return(err); } /* * Takes a string of the form: Field Title:Height,Width and parses out the info. */ void NickParseForInfo(Str255 input,long *height,long *width,Str255 fieldName) { Str31 token; UPtr spot; short i; spot = input+1; PToken(input,token,&spot,":"); StringToNum(PToken(input,token,&spot,","),height); // Get num of lines for field /* * Get width for field: 1 for full width, 2 for half width (currently not used) */ StringToNum(PToken(input,token,&spot,","),width); for (i=1;i<= *input;i++) { if (input[i]==':') break; } PCopy(fieldName,input); fieldName[0]=i; } /************************************************************************ * GetCurrentName - get the currently selected nickname ************************************************************************/ void GetName (short i,short *nickNum,short *whichFile) { if (i<0) { *nickNum = -1; *whichFile = -1; return; } else if (GetSelectedABNick (i, whichFile, nickNum)) ; } OSErr MyFSpMoveToTrash(const FSSpec *spec) { short theVRef; long theDirID; FSSpec trashSpec,tempSpec,uniqueSpec; OSErr theErr = noErr; // Move file to trash FindFolder(spec->vRefNum,kTrashFolderType,false,&theVRef,&theDirID); FSMakeFSSpec(theVRef,theDirID,"\p",&trashSpec); theErr = FSpCatMove(spec,&trashSpec); // If we don't have a problem, just leave. if (!theErr) return (noErr); // If the file already exists in the trash, load up our tempSpec if (theErr == dupFNErr) FSMakeFSSpec(theVRef,theDirID,spec->name,&tempSpec); uniqueSpec = tempSpec; theErr = UniqueSpec(&uniqueSpec,27); if (!theErr) theErr = FSpRename(&tempSpec,uniqueSpec.name); // We've successfully renamed the file in the trash, now we have to really trash our file if (theErr == noErr) { FindFolder(spec->vRefNum,kTrashFolderType,false,&theVRef,&theDirID); FSMakeFSSpec(theVRef,theDirID,"\p",&trashSpec); theErr = FSpCatMove(spec,&trashSpec); } return (theErr); } /********************************************************************** * TopCompositionWindow - topmost composition window, not counting current window **********************************************************************/ MyWindowPtr TopCompositionWindow(Boolean skipCurrent, Boolean dontCreate) { WindowPtr winWP = FrontWindow_(); MyWindowPtr win; if (skipCurrent) { while (winWP && (IsFloating(winWP) || !IsWindowVisible(winWP) || GetWindowKind(winWP)==FIND_WIN)) winWP = GetNextWindow(winWP); winWP = GetNextWindow(winWP); } while (winWP && (IsFloating(winWP) || !IsWindowVisible(winWP) || GetWindowKind(winWP)==FIND_WIN)) winWP = GetNextWindow(winWP); win = GetWindowMyWindowPtr(winWP); if (!IsKnownWindowMyWindow(winWP) || GetWindowKind(winWP)!=COMP_WIN || SumOf(Win2MessH(win))->state==SENT || !IsWindowVisible(winWP) || SumOf(Win2MessH(win))->state==BUSY_SENDING) { if (dontCreate) return (nil); win = DoComposeNew(0); #ifdef TWO if (win) { ApplyDefaultStationery(win,True,True); UpdateSum(Win2MessH(win),SumOf(Win2MessH(win))->offset,SumOf(Win2MessH(win))->length); } #endif } return(win); } /************************************************************************ * ************************************************************************/ void AliasesFixFont(void) { #ifdef BETA BIG UGLY ERROR #endif } /************************************************************************ * AskNickname - ask the user for a new nickname. ************************************************************************/ #ifdef VCARD NickReplaceEnum AskNickname (Handle addresses, FSSpec *vcardSpec, short ab) #else NickReplaceEnum AskNickname (Handle addresses, short ab) #endif { NickReplaceEnum nre; Str255 nickname, fullName; SAVE_PORT; nre = nrNone; // Figure out the default names from the addresses *nickname = 0; *fullName = 0; if (addresses) { UniqBinAddr(addresses); SortBinAddr(addresses); NickSuggest (addresses, nickname, fullName, false, 0); } if (!*nickname) MakeUniqueNickname (ab, nickname); // Truncate the size of the nickname if it is too beefy *nickname = MIN (*nickname, sizeof (Str31) - 1); // Display either the name or group dialog boxes depending on whether or not we have a real name if (CountAddresses (addresses, 2) < 2) #ifdef VCARD nre = DoNicknameDialog (addresses, vcardSpec, ab, nickname, fullName); #else nre = DoNicknameDialog (addresses, ab, nickname, fullName); #endif else nre = DoGroupNicknameDialog (addresses, ab, nickname, fullName); REST_PORT; return (nre); } #ifdef VCARD NickReplaceEnum DoNicknameDialog (Handle addresses, FSSpec *vcardSpec, short ab, PStr nickname, PStr fullName) #else NickReplaceEnum DoNicknameDialog (Handle addresses, short ab, PStr nickname, PStr fullName) #endif { DialogPtr dgPtr; MyWindowPtr dgPtrWin; NickReplaceEnum nre; PETEDocInitInfo pdi; MakeAddressBookEntryHitType mabeHit; Handle shortAddress, expansion, notes; Str255 workingName, firstName, lastName; OSErr theError; short *nickFileIndexArray, nick, item; Boolean needFile, makeRecip, done; extern ModalFilterUPP DlgFilterUPP; theError = noErr; nre = nrAdd; ParseFirstLast (fullName, firstName, lastName); needFile = true; nickFileIndexArray = GetNickFileArray (&needFile); SetDialogFont (SmallSysFontID ()); dgPtrWin = GetNewMyDialog (NEW_NICK_DLOG, nil, nil, InFront); dgPtr = GetMyWindowDialogPtr (dgPtrWin); SetDialogFont (0); if (!dgPtrWin || !dgPtr) theError = userCanceledErr; //fix the controls and setup all the PETE user panes if (!theError) { SetPort_ (GetWindowPort (GetDialogWindow (dgPtr))); ConfigFontSetup (dgPtrWin); ReplaceAllControls (dgPtr); DefaultPII (dgPtrWin, false, peNoStyledPaste | peClearAllReturns, &pdi); theError = CreatePeteUserPaneWithQDTextAndScanning (dgPtr, NEW_NICK_NAME, peNoStyledPaste | peClearAllReturns, &pdi, true, nickHighlight | nickComplete | nickSpaces, 0); } if (!theError) theError = CreatePeteUserPaneWithQDTextAndScanning (dgPtr, NEW_NICK_FULL_NAME, peNoStyledPaste | peClearAllReturns, &pdi, true, nickNoScan, 0); if (!theError) theError = CreatePeteUserPaneWithQDTextAndScanning (dgPtr, NEW_NICK_FIRST_NAME, peNoStyledPaste | peClearAllReturns, &pdi, true, nickNoScan, 0); if (!theError) theError = CreatePeteUserPaneWithQDTextAndScanning (dgPtr, NEW_NICK_LAST_NAME, peNoStyledPaste | peClearAllReturns, &pdi, true, nickNoScan, 0); if (!theError) theError = CreatePeteUserPaneWithQDTextAndScanning (dgPtr, NEW_NICK_ADDRESS, peNoStyledPaste | peClearAllReturns, &pdi, true, nickHighlight | nickComplete | nickSpaces, kNickScanAllAliasFiles); // Set the initial text for each PETE user pane if (!theError) { dgPtrWin->pte = GetPeteDItem (dgPtrWin, NEW_NICK_NAME); (void) SetKeyboardFocus (GetDialogWindow (dgPtr), GetDItemCtl (dgPtr, NEW_NICK_NAME), kControlEditTextPart); SetPeteDItemText (dgPtrWin, NEW_NICK_NAME, nickname); SetPeteDItemText (dgPtrWin, NEW_NICK_FULL_NAME, fullName); SetPeteDItemText (dgPtrWin, NEW_NICK_FIRST_NAME, firstName); SetPeteDItemText (dgPtrWin, NEW_NICK_LAST_NAME, lastName); if (addresses) { shortAddress = nil; SuckPtrAddresses (&shortAddress, LDRef (addresses) + 1, **addresses, false, True, False, nil); UL (addresses); if (shortAddress && *shortAddress) PeteSetString (*shortAddress, GetPeteDItem (dgPtrWin, NEW_NICK_ADDRESS)); ZapHandle(shortAddress); } // Fiddle with the rest of the controls SetDItemState (dgPtr, NEW_NICK_RECIP, false); if (!HasFeature (featureMultipleNicknameFiles)) { HideDialogItem (dgPtr, NEW_NICK_IN_ADDRESS); needFile = false; } BuildAddressBookMenu (dgPtr, NEW_NICK_FILE, nickFileIndexArray, needFile, &ab); SetAliasFileToScan (GetPeteDItem (dgPtrWin, NEW_NICK_NAME), ab); SetAliasFileToScan (GetPeteDItem (dgPtrWin, NEW_NICK_ADDRESS), ab); HiliteButtonOne (dgPtr); PeteSelectAll (dgPtrWin, dgPtrWin->pte); } // Run the dialog if (!theError) { StartMovableModal (dgPtr); ShowWindow (GetDialogWindow (dgPtr)); done = false; while (!done) { SetGreyControl (GetDItemCtl (dgPtr, NEW_NICK_ADD_DETAILS), !PeteLen (GetPeteDItem (dgPtrWin, NEW_NICK_NAME)) || !PeteLen (GetPeteDItem (dgPtrWin, NEW_NICK_ADDRESS))); SetGreyControl (GetDItemCtl (dgPtr, NEW_NICK_OK), !PeteLen (GetPeteDItem (dgPtrWin, NEW_NICK_NAME)) || !PeteLen (GetPeteDItem (dgPtrWin, NEW_NICK_ADDRESS))); CommandPeriod = false; MovableModalDialog (dgPtr, DlgFilterUPP, &item); switch (item) { case NEW_NICK_RECIP: SetDItemState (dgPtr, NEW_NICK_RECIP, !GetDItemState (dgPtr, NEW_NICK_RECIP)); break; case NEW_NICK_FILE: HitAddressBookMenu (dgPtrWin, NEW_NICK_FILE, nickFileIndexArray, needFile); break; case NEW_NICK_SWAP_NAMES: GetPeteDItemText (dgPtrWin, NEW_NICK_FIRST_NAME, firstName); GetPeteDItemText (dgPtrWin, NEW_NICK_LAST_NAME, lastName); SetPeteDItemText (dgPtrWin, NEW_NICK_FIRST_NAME, lastName); SetPeteDItemText (dgPtrWin, NEW_NICK_LAST_NAME, firstName); break; } GetPeteDItemText (dgPtrWin, NEW_NICK_NAME, workingName); ab = HasFeature (featureMultipleNicknameFiles) ? nickFileIndexArray[GetDItemState(dgPtr, NEW_NICK_FILE) - 1] : 0; if (item == CANCEL_ITEM || item == NEW_NICK_CANCEL || item == NEW_NICK_OK || item == NEW_NICK_ADD_DETAILS) done = true; if ((item == NEW_NICK_OK || item == NEW_NICK_ADD_DETAILS) && (nre = BadNickname (workingName, nickname, ab, -1, addresses==nil, true, false)) == nrDifferent) { PeteSelectAll (dgPtrWin, dgPtrWin->pte); done = false; } } mabeHit = mabeHitOther; if (item == NEW_NICK_OK || item == NEW_NICK_ADD_DETAILS) { Handle typedAddresses; mabeHit = item == NEW_NICK_OK ? mabeHitOK : mabeHitAddDetails; // Grab the info from the typed fields makeRecip = GetDItemState(dgPtr,NEW_NICK_RECIP); GetPeteDItemText (dgPtrWin, NEW_NICK_NAME, nickname); GetPeteDItemText (dgPtrWin, NEW_NICK_FULL_NAME, fullName); GetPeteDItemText (dgPtrWin, NEW_NICK_FIRST_NAME, firstName); GetPeteDItemText (dgPtrWin, NEW_NICK_LAST_NAME, lastName); // Grab the typed addresses typedAddresses = GetPeteDItemTextH (dgPtrWin, NEW_NICK_ADDRESS); if (typedAddresses) { Tr (typedAddresses, "\015", ","); theError = SuckPtrAddresses (&expansion, LDRef(typedAddresses),GetHandleSize (typedAddresses),true,true,false,nil); } ZapHandle (typedAddresses); // Make a notes handle #ifdef VCARD if (!theError) /* if (vcardSpec && HasFeature (featureVCard)) { ZapHandle (expansion); theError = NicknameFromVCardFile (vcardSpec, &expansion, ¬es); } else */ notes = CreateSimpleNotes (fullName, firstName, lastName); if (!theError) nick = NewNickLow (expansion, notes, ab, nickname, makeRecip, nre, AB.inited ? false : true); #else if (!theError) { notes = CreateSimpleNotes (fullName, firstName, lastName); nick = NewNickLow (expansion, notes, ab, nickname, makeRecip, nre, AB.inited ? false : true); } #endif } DisposePeteUserPaneItem (dgPtrWin, NEW_NICK_NAME); DisposePeteUserPaneItem (dgPtrWin, NEW_NICK_FULL_NAME); DisposePeteUserPaneItem (dgPtrWin, NEW_NICK_FIRST_NAME); DisposePeteUserPaneItem (dgPtrWin, NEW_NICK_LAST_NAME); DisposePeteUserPaneItem (dgPtrWin, NEW_NICK_ADDRESS); EndMovableModal(dgPtr); DisposDialog_(dgPtr); if (!theError) { if (AB.inited) UpdateMyWindow(GetMyWindowWindowPtr(Win)); // redisplay AB in case dialog was on top PostMakeNicknameDialog (ab, nick, mabeHit); } } ZapPtr (nickFileIndexArray); if (theError) WarnUser (ALIAS_NEW_NICK_ERR, theError); return (item == NEW_NICK_OK || item == NEW_NICK_ADD_DETAILS ? nre : nrCancel); } // // CreateSimpleNotes // // Create a simple notes handle containing only a full, first and last name. // Handle CreateSimpleNotes (PStr fullName, PStr firstName, PStr lastName) { Str255 tag; Handle notes; OSErr theError; notes = NuHandle (0); theError = MemError (); if (!theError && *fullName) theError = AddAttributeValuePair (notes, GetRString (tag, ABReservedTagsStrn + abTagName), &fullName[1], *fullName); if (!theError && *firstName) theError = AddAttributeValuePair (notes, GetRString (tag, ABReservedTagsStrn + abTagFirst), &firstName[1], *firstName); if (!theError && *lastName) theError = AddAttributeValuePair (notes, GetRString (tag, ABReservedTagsStrn + abTagLast), &lastName[1], *lastName); if (theError) ZapHandle (notes); return (notes); } NickReplaceEnum DoGroupNicknameDialog (Handle addresses, short ab, PStr nickname, PStr fullName) { DialogPtr dgPtr; MyWindowPtr dgPtrWin; NickReplaceEnum nre; PETEDocInitInfo pdi; MakeAddressBookEntryHitType mabeHit; Handle expansion, notes; Str255 workingName, tag; OSErr theError; short *nickFileIndexArray, nick, item; Boolean needFile, makeRecip, done; extern ModalFilterUPP DlgFilterUPP; theError = noErr; nre = nrAdd; needFile = true; nickFileIndexArray = GetNickFileArray (&needFile); SetDialogFont (SmallSysFontID ()); dgPtrWin = GetNewMyDialog (NEW_GROUP_DLOG, nil, nil, InFront); dgPtr = GetMyWindowDialogPtr (dgPtrWin); SetDialogFont (0); if (!dgPtrWin || !dgPtr) theError = userCanceledErr; //fix the controls and setup all the PETE user panes if (!theError) { SetPort_ (GetWindowPort (GetDialogWindow (dgPtr))); ConfigFontSetup (dgPtrWin); ReplaceAllControls (dgPtr); DefaultPII (dgPtrWin, false, peNoStyledPaste | peClearAllReturns, &pdi); theError = CreatePeteUserPaneWithQDTextAndScanning (dgPtr, NEW_GROUP_NICK_NAME, peNoStyledPaste | peClearAllReturns, &pdi, true, nickHighlight | nickComplete | nickSpaces, 0); } if (!theError) theError = CreatePeteUserPaneWithQDTextAndScanning (dgPtr, NEW_GROUP_GROUP_NAME, peNoStyledPaste | peClearAllReturns, &pdi, true, nickNoScan, 0); if (!theError) theError = CreatePeteUserPaneWithQDTextAndScanning (dgPtr, NEW_GROUP_ADDRESSES, peNoStyledPaste | peVScroll | peUseHLineWidth, &pdi, false,nickHighlight | nickComplete | nickSpaces, kNickScanAllAliasFiles); // Set the initial text for each PETE user pane if (!theError) { dgPtrWin->pte = GetPeteDItem (dgPtrWin, NEW_GROUP_NICK_NAME); (void) SetKeyboardFocus (GetDialogWindow (dgPtr), GetDItemCtl (dgPtr, NEW_GROUP_NICK_NAME), kControlEditTextPart); SetPeteDItemText (dgPtrWin, NEW_GROUP_NICK_NAME, nickname); SetPeteDItemText (dgPtrWin, NEW_GROUP_GROUP_NAME, fullName); if (addresses) { FlattenListWith (addresses, '\015'); PeteSetTextPtr (GetPeteDItem (dgPtrWin, NEW_GROUP_ADDRESSES), LDRef (addresses), GetHandleSize (addresses)); UL (addresses); } // Fiddle with the rest of the controls SetDItemState (dgPtr, NEW_GROUP_RECIP, false); if (!HasFeature (featureMultipleNicknameFiles)) { HideDialogItem (dgPtr, NEW_GROUP_IN_ADDRESSES); needFile = false; } BuildAddressBookMenu (dgPtr, NEW_GROUP_FILE, nickFileIndexArray, needFile, &ab); SetAliasFileToScan (GetPeteDItem (dgPtrWin, NEW_GROUP_NICK_NAME), ab); SetAliasFileToScan (GetPeteDItem (dgPtrWin, NEW_GROUP_ADDRESSES), ab); HiliteButtonOne (dgPtr); PeteSelectAll (dgPtrWin, dgPtrWin->pte); } // Run the dialog if (!theError) { StartMovableModal (dgPtr); ShowWindow (GetDialogWindow (dgPtr)); done = false; while (!done) { SetGreyControl (GetDItemCtl (dgPtr, NEW_GROUP_ADD_DETAILS), !PeteLen (GetPeteDItem (dgPtrWin, NEW_GROUP_NICK_NAME)) || !PeteLen (GetPeteDItem (dgPtrWin, NEW_GROUP_ADDRESSES))); SetGreyControl (GetDItemCtl (dgPtr, NEW_GROUP_OK), !PeteLen (GetPeteDItem (dgPtrWin, NEW_GROUP_NICK_NAME)) || !PeteLen (GetPeteDItem (dgPtrWin, NEW_GROUP_ADDRESSES))); CommandPeriod = false; MovableModalDialog (dgPtr, DlgFilterUPP, &item); switch (item) { case NEW_GROUP_RECIP: SetDItemState (dgPtr, NEW_GROUP_RECIP, !GetDItemState (dgPtr, NEW_GROUP_RECIP)); break; case NEW_GROUP_FILE: HitAddressBookMenu (dgPtrWin, NEW_GROUP_FILE, nickFileIndexArray, needFile); break; } GetPeteDItemText (dgPtrWin, NEW_GROUP_NICK_NAME, workingName); ab = HasFeature (featureMultipleNicknameFiles) ? nickFileIndexArray[GetDItemState(dgPtr, NEW_GROUP_FILE) - 1] : 0; if (item == CANCEL_ITEM || item == NEW_GROUP_CANCEL || item == NEW_GROUP_OK || item == NEW_GROUP_ADD_DETAILS) done = true; if ((item == NEW_GROUP_OK || item == NEW_GROUP_ADD_DETAILS) && (nre = BadNickname (workingName, fullName, ab, -1, addresses==nil,true,false)) == nrDifferent) { PeteSelectAll (dgPtrWin, dgPtrWin->pte); done = false; } } mabeHit = mabeHitOther; if (item==NEW_GROUP_OK || item == NEW_GROUP_ADD_DETAILS) { Handle typedAddresses; mabeHit = item == NEW_GROUP_OK ? mabeHitOK : mabeHitAddDetails; makeRecip = GetDItemState(dgPtr,NEW_GROUP_RECIP); GetPeteDItemText (dgPtrWin, NEW_GROUP_NICK_NAME, nickname); GetPeteDItemText (dgPtrWin, NEW_GROUP_GROUP_NAME, fullName); // Grab the typed addresses typedAddresses = GetPeteDItemTextH (dgPtrWin, NEW_GROUP_ADDRESSES); if (typedAddresses) { Tr (typedAddresses, "\015", ","); theError = SuckPtrAddresses (&expansion, LDRef(typedAddresses),GetHandleSize (typedAddresses),true,true,false,nil); } ZapHandle (addresses); // Make a notes handle if (!theError) { notes = NuHandle (0); theError = MemError (); } if (!theError && *fullName) theError = AddAttributeValuePair (notes, GetRString (tag, ABReservedTagsStrn + abTagName), &fullName[1], *fullName); if (!theError) nick = NewNickLow (expansion, notes, ab, nickname, makeRecip, nre, AB.inited ? false : true); } DisposePeteUserPaneItem (dgPtrWin, NEW_GROUP_NICK_NAME); DisposePeteUserPaneItem (dgPtrWin, NEW_GROUP_GROUP_NAME); DisposePeteUserPaneItem (dgPtrWin, NEW_GROUP_ADDRESSES); EndMovableModal(dgPtr); DisposDialog_(dgPtr); if (!theError) { if (AB.inited) UpdateMyWindow(GetMyWindowWindowPtr(Win)); // redisplay AB in case dialog was on top PostMakeNicknameDialog (ab, nick, mabeHit); } } ZapPtr (nickFileIndexArray); if (theError) WarnUser (ALIAS_NEW_NICK_ERR, theError); return (item==NEW_GROUP_OK || item == NEW_GROUP_ADD_DETAILS ? nre : nrCancel); } // // CreatePeteUserPaneWithQDTextAndScanning // // Abusively long, but it saves us some typing and code size in the long run. // OSErr CreatePeteUserPaneWithQDTextAndScanning (DialogPtr theDialog, short item, uLong flags, PETEDocInitInfoPtr initInfo, Boolean noWrap, NickScanType nickScan, short aliasIndex) { PETEHandle pte; if (pte = CreatePeteUserPane (GetDItemCtl (theDialog, item), flags, initInfo, noWrap)) { PeteFontAndSize (pte,GetPortTextFont(GetQDGlobalsThePort()),GetPortTextSize(GetQDGlobalsThePort())); if (nickScan != nickNoScan) SetNickScanning (pte, nickScan, aliasIndex, nil, nil, nil); } return (pte ? noErr : userCanceledErr); } void PostMakeNicknameDialog (short ab, short nick, MakeAddressBookEntryHitType mabeHit) { VLNodeInfo data; VLNodeID nodeID; nodeID = MakeNodeID (ab, nick); switch (mabeHit) { case mabeHitOK: if (AB.inited) { ABTickle (); // InvalidListView (&NickList); // ABNickLVSelect (nodeID, true); // LVDraw (&NickList,nil,false, false); } break; case mabeHitAddDetails: if (AB.inited) InvalidListView (&NickList); OpenABWin (nil); // Massage the item name so it can be found in the list view if (AB.inited) { if ((*Aliases)[ab].collapsed) { LVGetItemWithNodeID (&NickList, MakeABNodeID (ab), &data); LVExpand (&NickList, MakeABNodeID (ab), data.name, true); } ABNickLVSelect (nodeID, false); LVDraw (&NickList,nil,false,false); } break; } } /********************************************************************** * NickSuggest - suggest a name for an address and extract the real name **********************************************************************/ void NickSuggest(BinAddrHandle addresses, PStr name,PStr realName,Boolean uniq,short fmt) { Str255 scratch; Str255 firstAddress; Str63 first, last; short i; *name = 0; *realName = 0; /* * emptiness */ if (!addresses || !*addresses || !**addresses) return; /* * more than one address? */ if ((*addresses)[**addresses+2]) return; /* * only one address! */ PSCopy(scratch,*addresses); /* * extract name portion */ BeautifyFrom(scratch); if (!*scratch || *scratch > 63) return; PCopy(realName,scratch); PCopy(firstAddress,*addresses); PCopy(name,realName); // if name is same as address, it's not really // a name, so don't bother trying to process it // as though it were if (StringSame(realName,firstAddress)) *realName = 0; else { ParseFirstLast(name,first,last); if (GetPrefLong(PREF_NICK_GEN_OPTIONS)&kNickGenOptLastFirst) { PCopy(name,last); if (*name) PCatC(name,' '); PCat(name,first); } else JoinFirstLast(name,first,last); } // compose using the format, if any if (fmt) { ComposeRString(scratch,fmt,name); PCopy(name,scratch); } /* * Sanitize */ SanitizeFN(name,name,NICK_BAD_CHAR,NICK_REP_CHAR,false); *name = MIN(*name,30); // make it unique if (uniq) { PSCopy(last,name); for (i=1;IsAnyNickname(last);i++) { NumToString(i,first); PCopy(scratch,name); *scratch = MIN(30-*first,*scratch); PCat(scratch,first); PCopy(last,scratch); } PCopy(name,last); } } short *GetNickFileArray (Boolean *needFile) { short *nickFileIndexArray, item;; nickFileIndexArray = nil; // we only need the file menu if another writeable file exists //Enhanced Address Book - prevent users from creating a new nickname FILE. if (HasFeature (featureMultipleNicknameFiles)) { if (*needFile) { *needFile = False; for (item = 1; item < NAliases; item++) if (!(*Aliases)[item].ro) { *needFile = True; break; } } // We're going to build a small array that matches each element of the file popup. In // each array element we'll store the "real" 'which' value for that menu item to be used // when the user has specified a particular nickname file. We're doing this to prevent // an offset problem when the user has plug-in nickname files. if (*needFile) nickFileIndexArray = NuPtr (sizeof (short) * NAliases); } else *needFile = false; return (nickFileIndexArray); } void BuildAddressBookMenu (DialogPtr theDialog, short addressBookMenuItem, short *nickFileIndexArray, Boolean needFile, short *which) { ControlHandle theControl; MenuHandle mh; short *nickFileIndexArrayPtr, ab, totalAliases; if (theControl = GetDItemCtl (theDialog, addressBookMenuItem)) if (mh = GetControlPopupMenuHandle (theControl)) if (!needFile) HideDialogItem (theDialog, addressBookMenuItem); else { nickFileIndexArrayPtr = nickFileIndexArray; // The address book menu will consist of only the following: // Eudora Nicknames // Regular Address Books // History List totalAliases = NAliases; for (ab = 0; ab < totalAliases; ++ab) if (IsEudoraAddressBook (ab) || IsRegularAddressBook (ab) || (IsHistoryAddressBook (ab) && !PrefIsSet (PREF_NICK_CACHE_NOT_VISIBLE))) { MyAppendMenu (mh, (*Aliases)[ab].spec.name); if ((*Aliases)[ab].ro) DisableItem (mh, CountMenuItems (mh)); *nickFileIndexArrayPtr++ = ab; } SetControlMaximum (theControl, CountMenuItems (mh)); SetControlMinimum (theControl, 1); SetControlValue (theControl, *which + 1); } } void HitFileNickRadioButton (DialogPtr theDialog, short itemHit, short addressBookItem, short nicknameItem, short addressBookMenuItem, short recipientItem) { short newFileCheck; newFileCheck = GetDItemState (theDialog, addressBookItem); if ((newFileCheck && itemHit == nicknameItem) || (!newFileCheck && itemHit == addressBookItem)) { // Currently checked, need to undim items if (newFileCheck) { SetGreyControl (GetDItemCtl (theDialog, addressBookMenuItem), false); SetGreyControl (GetDItemCtl (theDialog, recipientItem), false); } else // Currently unchecked, need to dim items. { SetGreyControl (GetDItemCtl (theDialog, addressBookMenuItem), true); SetGreyControl (GetDItemCtl (theDialog, recipientItem), true); } SetDItemState (theDialog, addressBookItem, !newFileCheck); SetDItemState (theDialog, nicknameItem, newFileCheck); } } void HitAddressBookMenu (MyWindowPtr dgPtrWin, short addressBookMenuItem, short *nickFileIndexArray, Boolean needFile) { long selStart, selEnd; if (needFile) { if (CurSelectionIsTypeAheadText (dgPtrWin->pte)) { ResetNicknameTypeAhead (dgPtrWin->pte); ResetNicknameHiliting (dgPtrWin->pte); if (!PeteGetTextAndSelection (dgPtrWin->pte, nil, &selStart, &selEnd)) if (selStart != selEnd) { PeteDelete (dgPtrWin->pte, selStart, selEnd); NicknameHilitingUnHiliteField (dgPtrWin->pte); } } SetAliasFileToScan (dgPtrWin->pte, nickFileIndexArray[GetDItemState(GetMyWindowDialogPtr (dgPtrWin), addressBookMenuItem) - 1]); } } /************************************************************************ * BadNickname - is the nickname defective? ************************************************************************/ NickReplaceEnum BadNickname(UPtr candidate,UPtr veteran,short which,short nick,Boolean justRename,Boolean collisionCheck,Boolean folder) { Str31 verboten; UPtr spot,end,key; Str31 noWhiteCand, noWhiteVet; short ab, foundNick, i; if (!*candidate) return(nrDifferent); // If the strings hasn't changed on a rename, don't test it if (justRename && StringSame (candidate, veteran)) return (nrOk); // folder? if (folder) { if (*candidate>31) { WarnUser(NICKFILE_TOO_LONG,0); return(nrDifferent); } if (PIndex(candidate,':')) { WarnUser(NICKFILE_BADCHAR,0); return(nrDifferent); } for (i=NAliases-1;i>=0;i--) { PSCopy(verboten,(*Aliases)[i].spec.name); if (StringSame(verboten,candidate)) { WarnUser(NICKFILE_DUPLICATE,0); return(nrDifferent); } } if (EqualStrRes(candidate,ALIAS_FILE) || EqualStrRes(candidate,USA_EUDORA_NICKNAMES) || EqualStrRes(candidate,FILE_ALIAS_NICKNAMES)) { WarnUser(NICKFILE_DUPLICATE,0); return(nrDifferent); } return(nrOk); } // nickname if (*candidate>MAX_NICKNAME) { ComposeStdAlert (Note, ALIAS_TOO_LONG, MAX_NICKNAME); return(nrDifferent); } GetRString(verboten,ALIAS_VERBOTEN); end = verboten+*verboten+1; for (key=candidate+1;key<=candidate+*candidate;key++) for (spot=verboten+1;spotpte, hs.stop, hs.stop); PetePrepareUndo (win->pte, peUndoPaste, -1, -1, &start, nil); index = 1; while (GetSelectedABNick (index++, &ab, &nick)) if (nick >= 0) { InsertCommaIfNeedBe (TheBody, &hs); GetNicknameNamePStr (ab, nick, nameStr); InsertAlias (win->pte, &hs, nameStr, wantExpansion, start, false); } ShowMyWindow (GetMyWindowWindowPtr (win)); UpdateSum (messH, SumOf(messH)->offset, SumOf(messH)->length); SetPort_ (oldPort); } return win; } /********************************************************************** * NickWDragVDivider - Handle dragging of the divider between the list * and the data **********************************************************************/ void NickWDragVDivider(Point pt) { Rect div, bounds; Point newPt; long tempValue; div = VertDivider; bounds = Win->contR; bounds.right = RectWi(Win->contR) - MinTabWidth; bounds.left = MinListWidth; if (bounds.left > bounds.right) { bounds.left = div.left; bounds.right = div.right; } if (div.right > bounds.right) bounds.right = div.right; newPt = DragDivider(pt,&div,&bounds,Win); // resize? if (newPt.h) { tempValue = (100 * (newPt.h-2*INSET-INSET/2))/(Win->contR.right - Win->contR.left); SetPrefLong(ALIAS_NICK_LIST_PRCT,MIN(tempValue,750)); MyWindowDidResize(Win,nil); } } // Are any nicknames dirty? Boolean AnyNicknamesDirty (short ab) { short whichFile; short totalAliases = NAliases; short totalNicks; short firstFile = 0; short i; if (ab != kAddressBookUndefined) { firstFile = ab; totalAliases = ab + 1; } for (whichFile=firstFile;whichFile=*text;spot--) { if (*spot=='\015') { *--copy = ' '; *--copy = ','; } else *--copy = *spot; } ZeroScrap(); PutScrap(size+newlines,'TEXT',LDRef(text)); } } } ZapHandle(text); } } /********************************************************************** * PrintAddressBookWindow - print nicknames from the nicknames window * Brand-new strategy here: Let Pete do it! **********************************************************************/ OSErr PrintAddressBookWindow (Boolean select, Boolean now) { OSErr err = noErr; MyWindowPtr win; WindowPtr winWP; short which; // make the title for the window, this will go in the print header ABPrintMakeTitle(GlobalTemp,select); // Make a text window to dump the stuff to if (!(win=OpenText(nil,nil,nil,nil,false,GlobalTemp,true,false))) WarnUser(PRINT_FAILED,0); else { winWP = GetMyWindowWindowPtr (win); (*PeteExtra(win->pte))->spelled = sprNeverSpell; PeteCalcOff(win->pte); PETEAllowUndo(PETE,win->pte,false,false); SetNickScanning (win->pte, nickHighlight, kNickScanAllAliasFiles, nil, nil, nil); for (which=0;whichpte); // Print the text window win->dirty = TheWorldAccordingToMarthaStewart; #ifdef DEBUG if (RunType!=Production && !(CurrentModifiers()&optionKey)) { PeteCalcOn(win->pte); ShowMyWindow(winWP); return noErr; } else #endif err = PrintOneMessage(win,false,now); // Close the text window NoSaves = true; win->isDirty = false; PeteCleanList (win->pte); CloseMyWindow(winWP); NoSaves = false; if (err) WarnUser(PART_PRINT_FAIL,err); } return err; } /********************************************************************** * ABPrintMakeTitle - make up a title for printing **********************************************************************/ void ABPrintMakeTitle(PStr title, Boolean select) { WindowPtr winWP = GetMyWindowWindowPtr(Win); GetWTitle(winWP,title); } /********************************************************************** * PrintNickFile - print nicknames from one file **********************************************************************/ OSErr PrintNickFile (short which, Boolean select, PETEHandle pte) { Str255 title; short total, ab, nick, count, firstIndex, // Index of the first nickname in the chosen address book i, myCount = 0; NickPrintStuff myStuff; short **sort; // prepare para info Res2PInfo(NICK_PRINT_MARG1,pte,nil,&myStuff.pi1); Res2PInfo(NICK_PRINT_MARG2,pte,nil,&myStuff.pi2); Res2PInfo(NICK_PRINT_MARG3,pte,nil,&myStuff.pi3); myStuff.pte = pte; total = 0; if (select) { count = LVCountSelection (&NickList); for (i = 1; i <= count; ++i) { GetSelectedABNick (i, &ab, &nick); if (ab == which) { if (nick != kNickUndefined) { if (++myCount == 1) firstIndex = i; } else { select = false; break; } } } } if (myCount == 0 && select) return (noErr); BuildNickPrintTitle (which, select, title); if (PeteLen(pte)) PInsertC(title,sizeof(title),'\f',title+1); PeteAppendText(title+1,*title,pte); PeteAppendText(Cr+1,1,pte); PeteInsertRule(pte,kPETELastPara,0,0,false,true,false); count = (*Aliases)[which].theData ? (GetHandleSize_ ((*Aliases)[which].theData) / sizeof (NickStruct)) : 0; total = 0; for (nick = 0; nick < count; nick++) if (!(*((*Aliases)[which].theData))[nick].deleted) ++total; // The list will always have the file title as the first element and therefore the // count will be the total nickname count + 1; // However the first element (the title) is not printed. if (!total) // If nickname file is blank, DON'T print it! return (noErr); if (select) { for (i = firstIndex; !CommandPeriod && myCount; ++i) { GetSelectedABNick (i, &ab, &nick); PrintNickname ( &myStuff, ab, nick ); myCount--; } } else { // Make sure the address book is sorted SortAddressBook(which); sort = (*Aliases)[which].sortData; if (!sort) return memFullErr; count = (*Aliases)[which].theData ? (GetHandleSize_ ((*Aliases)[which].theData) / sizeof (NickStruct)) : 0; for (nick = 0; !CommandPeriod && nick < count; ++nick) if (!(*((*Aliases)[which].theData))[(*sort)[nick]].deleted) PrintNickname ( &myStuff, which, (*sort)[nick] ); } return (noErr); // yes, we should probably do more with error checking, but the old } // version of this routine could not generate anything but 'noErr' // // PrintNickname // void PrintNickname (NickPrintStuffPtr myStuffP, short ab, short nick) { Handle tempHandle; GetNicknameNamePStr (ab, nick, myStuffP->name); myStuffP->addresses = nil; if (tempHandle = GetNicknameData (ab, nick, true, true)) SuckAddresses (&myStuffP->addresses, tempHandle, true, true, false, nil); myStuffP->ab = ab; myStuffP->nick = nick; PrintANick (myStuffP); ZapHandle (myStuffP->addresses); } /********************************************************************** * PrintANick - print a single nickname **********************************************************************/ OSErr PrintANick(NickPrintStuffPtr myStuffP) { OSErr err = noErr; /* * print the nickname */ if (err=NickPrintName(myStuffP->pte, &myStuffP->pi1, myStuffP->name)) return err; /* * print things in tab order */ IterateTabPanes (Tabs, ABPrintPaneProc, myStuffP); // and a blank line, just for fun if (err = PETEInsertParaPtr(PETE,myStuffP->pte,kPETELastPara,nil,Cr+1,*Cr,nil)) return err; err = PetePlainParaAt(myStuffP->pte,PeteLen(myStuffP->pte)-1,PeteLen(myStuffP->pte)); return err; } /********************************************************************** * ABPrintPaneProc - interator over tabs. Print the tab name, then iterate * over the fields **********************************************************************/ Boolean ABPrintPaneProc (ControlHandle tabControl, ControlHandle tabPane, short tabIndex, NickPrintStuffPtr myStuffP) { myStuffP->foundOne = false; myStuffP->tabPane = tabPane; IterateTabPaneObjectsUL(tabPane,ABPrintObjectProc,myStuffP); return false; } /********************************************************************** * ABPrintObjectProc - iterate over objects in the tab **********************************************************************/ Boolean ABPrintObjectProc (TabObjectPtr objectPtr, NickPrintStuffPtr myStuffP) { Str255 value; Str255 s; if (objectPtr->type == fieldObject) { if (EqualStrRes(objectPtr->tag,ABReservedTagsStrn+abTagEmail)) { if (myStuffP->addresses && **myStuffP->addresses) { // print tab name if we haven't already if (!myStuffP->foundOne) { GetControlTitle(myStuffP->tabPane,s); NickPrintName(myStuffP->pte, &myStuffP->pi2, s); myStuffP->foundOne = true; } NickPrintAddresses(myStuffP->pte,&myStuffP->pi3,objectPtr->shortName,myStuffP->addresses); } } else { // does it have a value? GetTaggedFieldValueStr (myStuffP->ab, myStuffP->nick, objectPtr->tag, value); if (*value) { // print tab name if we haven't already if (!myStuffP->foundOne) { Str255 paneTitle; GetControlTitle(myStuffP->tabPane,paneTitle); NickPrintName(myStuffP->pte, &myStuffP->pi2, paneTitle); myStuffP->foundOne = true; } // Now print name & value NickPrintNameAndValue(myStuffP->pte, &myStuffP->pi3, objectPtr->shortName, value); } } } return false; } /********************************************************************** * **********************************************************************/ OSErr NickPrintName(PETEHandle pte, PETEParaInfoPtr pinfop, PStr name) { Str255 s; OSErr err; PCopy(s,name); PCat(s,Cr); err = PETEInsertParaPtr(PETE,pte,kPETELastPara,pinfop,s+1,*s,nil); return err; } /********************************************************************** * **********************************************************************/ OSErr NickPrintAddresses(PETEHandle pte, PETEParaInfoPtr pinfop, PStr name, UHandle addresses) { OSErr err; short oldOffset = PeteLen(pte); err = PETEInsertParaPtr(PETE,pte,kPETELastPara,pinfop,name+1,*name,nil); if (!err) err = PeteInsertChar(pte,kPETELastPara,'\t',nil); FlattenListWith(addresses,'\015'); if (!err) err = PETEInsertTextHandle(PETE,pte,kPETELastPara,addresses,GetHandleSize(addresses),0,nil); if (!err) err = PeteInsertChar(pte,kPETELastPara,'\r',nil); //NicknameHilitingUpdateRange (pte, oldOffset, PeteLen(pte)); return err; } /********************************************************************** * **********************************************************************/ OSErr NickPrintNameAndValue(PETEHandle pte, PETEParaInfoPtr pinfop, PStr name, PStr value) { OSErr err; err = PETEInsertParaPtr(PETE,pte,kPETELastPara,pinfop,name+1,*name,nil); if (!err) err = PeteInsertChar(pte,kPETELastPara,'\t',nil); if (!err) err = PETEInsertTextPtr(PETE,pte,kPETELastPara,value+1,*value,nil); if (!err) err = PeteInsertChar(pte,kPETELastPara,'\r',nil); return err; } /********************************************************************** * BuildNickPrintTitle - print title for a nickname **********************************************************************/ void BuildNickPrintTitle(short which,Boolean select,PStr title) { Str63 fn; Str63 selectStr; if (select) GetRString(selectStr,SELECTED); else *selectStr = 0; PCopy(fn,(*Aliases)[which].spec.name); ComposeRString(title,NICK_HEAD_FMT,selectStr,fn); } // // GetAliasExpansionFor822Flavor // // This is derived from InsertAlias. Originally, this code was buried within the InsertAlias // function in 'nickexp.c', special-cased because there was some commonality to what that // function did. It made my head hurt so I moved it here. // Handle GetAliasExpansionFor822Flavor (short which, short index) { BinAddrHandle wordH, list; Str31 nameStr; long tempSize, addressSize;; GetNicknameNamePStr (which, index, nameStr); addressSize = *nameStr + 1; wordH = NuHandle (addressSize + 5); if (!wordH) { WarnUser (MEM_ERR, MemError ()); return (nil); } BlockMoveData (nameStr, *wordH, nameStr[0] + 1); (*wordH)[**wordH + 1] = (*wordH)[**wordH + 2] = 0; ExpandAliases (&list, wordH, 0, true); ZapHandle (wordH); // (jp) Bad ju-ju ahead... if the list is zero length, we correctly do not replace the existing typing // with nothing (what would be the point?), but we incorrectly lose the caret. if (list) { tempSize = GetHandleSize_ (list); if (tempSize == 1 && strlen (LDRef (list)) == 0) ZapHandle (list); else UL (list); } if (list) CommaList (list); return (list); } /********************************************************************** * NickAddrSetDragContents - add the right stuff to a drag **********************************************************************/ pascal OSErr NickAddrSetDragContents(PETEHandle pte,DragReference drag) { OSErr err = noErr; Handle text = nil,commaText=nil; long selStart,selEnd; err = PeteGetTextAndSelection(pte,&text,&selStart,&selEnd); if (err) return(err); commaText = NuHandle(0); if (!commaText) return(MemError()); err = HandPlusHand(text,commaText); if(err) {ZapHandle(commaText);return(err);} Tr(commaText,"\015",","); err = AddDragItemFlavor(drag, 1L, 'TEXT', LDRef(text) + selStart, selEnd - selStart, 0); if (!err) err = AddDragItemFlavor(drag, 1L, A822_FLAVOR, LDRef(commaText) + selStart, selEnd - selStart, 0); UL(commaText); // ZapHandle(text); ZapHandle(commaText); return(err); } /********************************************************************** * NickGetDragContents - get the right stuff from a drag **********************************************************************/ OSErr SuckDragAddresses(DragReference drag,Handle *addresses,Boolean leadingComma,Boolean trailingComma); pascal OSErr NickGetDragContents(PETEHandle pte,UHandle *theText, PETEStyleListHandle *theStyles, PETEParaScrapHandle *theParas, DragReference drag, long dropLocation) { OSErr err = handlerNotFoundErr; long theLen = PeteLen(pte); *theText = nil; err = SuckDragAddresses(drag,theText,dropLocation > 0 && theLen != 0,dropLocation < theLen); return(err); } /********************************************************************** * TheWorldAccordingToMarthaStewart - is a window dirty? **********************************************************************/ Boolean TheWorldAccordingToMarthaStewart(MyWindowPtr win) { return false; } void DoAddressBookExport (Boolean select) { TabFieldHandle fields; NickExportRec nickExport; FSSpec spec; Str255 windowTitle, tag; Str15 ctext; UPtr spot, end; OSErr theError; long creator; short ab; ModalFilterYDUPP filter=nil; GetRString (spec.name, UNTITLED_CSV); GetRString (windowTitle, NICK_SAVE_AS_TITLE); GetPref (ctext, PREF_CREATOR); if (*ctext != 4) GetRString (ctext, TEXT_CREATOR); BMD (ctext + 1, &creator, 4); if (theError = SFPutOpen (&spec, creator, 'TEXT', &nickExport.refNum, nil, filter, 0, nil, windowTitle, nil)) return; OpenProgress(); ProgressR (NoBar, 0, EXPORTING_NICKNAMES, EXPORTING_SUBTITLE, nil); nickExport.theError = noErr; // Stringy stuff GetRString (nickExport.eol, NICK_EXPORT_EOL); GetRString (nickExport.comma, NICK_EXPORT_COMMA); GetRString (nickExport.commaReplace, NICK_COMMA_REPLACE_CHAR); GetRString (nickExport.crReplace, NICK_CR_REPLACE_CHAR); // Initialize the accumulators theError = AccuInit (&nickExport.a); // Place the field names into the accumulator -- this is the first line written to the file. The nickname // field is always the first field, followed by the rest in tab field order if (!theError) theError = AccuAddStr (&nickExport.a, GetRString (tag, ABReservedTagsStrn + abNickname)); if (!theError) if (fields = GetExportableTabFieldStrings (Tabs)) { spot = LDRef (fields); end = spot + GetHandleSize (fields); while (spot < end && !theError) { if (*spot) { theError = AccuAddStr (&nickExport.a, nickExport.comma); if (!theError) theError = AccuAddStr (&nickExport.a, spot); } spot += (*spot + 2); } if (!theError) theError = AccuAddStr (&nickExport.a, nickExport.eol); ZapHandle (fields); } // If no address books are selected, we'll save them all. Otherwise, export only those that are selected. // In either case, field values are added in the same order as the field names -- nickname followed by tab order. for (ab = 0; ab < NAliases && !theError; ++ab) if (!IsPluginAddressBook (ab)) theError = ExportNickFile (&nickExport, ab, select); // Anything left to write to the file? if (!theError) theError = AccuWrite (&nickExport.a, nickExport.refNum); if (!theError) theError = TruncAtMark (nickExport.refNum); (void) MyFSClose (nickExport.refNum); AccuZap (nickExport.a); CloseProgress (); if (theError) WarnUser (NICK_EXPORT_FAIL, theError); } OSErr ExportNickFile (NickExportPtr nickExport, short which, Boolean select) { OSErr theError; short **sort, ab, nick, count, myCount, firstIndex, i; theError = noErr; myCount = 0; if (select) { count = LVCountSelection (&NickList); for (i = 1; i <= count; ++i) { GetSelectedABNick (i, &ab, &nick); if (ab == which) { if (ValidNickname (nick)) { if (++myCount == 1) firstIndex = i; } else { select = false; break; } } } } if (myCount == 0 && select) return (noErr); if (select) { for (i = firstIndex; !theError && !CommandPeriod && myCount; ++i) { GetSelectedABNick (i, &ab, &nick); theError = ExportNickname (nickExport, ab, nick); myCount--; } } else { // We'll sort before exporting, just to be nice SortAddressBook (which); sort = (*Aliases)[which].sortData; if (!sort) return (memFullErr); count = (*Aliases)[which].theData ? (GetHandleSize_ ((*Aliases)[which].theData) / sizeof (NickStruct)) : 0; for (nick = 0; !theError && !CommandPeriod && nick < count; ++nick) if (!(*((*Aliases)[which].theData))[(*sort)[nick]].deleted) theError = ExportNickname (nickExport, which, (*sort)[nick]); } return (theError); } OSErr ExportNickname (NickExportPtr nickExport, short ab, short nick) { Str255 nickname; OSErr theError; ProgressMessage (kpMessage, GetNicknameNamePStr (ab, nick, nickname)); CycleBalls (); // The nickname itself is always first theError = AccuAddStr (&nickExport->a, nickname); // Following the nickname are all the fields, in tab order if (!theError) { nickExport->ab = ab; nickExport->nick = nick; IterateTabObjectsUL (Tabs, ExportNicknameProc, nickExport); theError = nickExport->theError; } // Line termination if (!theError) theError = AccuAddStr (&nickExport->a, nickExport->eol); // Maybe write out what we've accumulated so far if (!theError && nickExport->a.offset > 16 K) { theError = AccuWrite (&nickExport->a, nickExport->refNum); nickExport->a.offset = 0; } return (theError); } Boolean ExportNicknameProc (TabObjectPtr objectPtr, NickExportPtr nickExport) { TextAddrHandle addresses; Str255 value; Handle tempHandle; OSErr theError; theError = noErr; // add the value if (objectPtr->flags & objectFlagExportable) { // lead off with a comma before each field value theError = AccuAddStr (&nickExport->a, nickExport->comma); if (EqualStrRes (objectPtr->tag, ABReservedTagsStrn + abTagEmail)) { addresses = nil; if (tempHandle = GetNicknameData (nickExport->ab, nickExport->nick, true, true)) { theError = SuckAddresses (&addresses, tempHandle, true, true, false, nil); if (!theError && addresses && **addresses) { CommaList (addresses); Tr (addresses, ",", nickExport->commaReplace); theError = AccuAddHandle (&nickExport->a, addresses); } } ZapHandle (addresses); } else { GetTaggedFieldValueStr (nickExport->ab, nickExport->nick, objectPtr->tag, value); if (*value) { TrLo (&value[1], *value, ",", nickExport->commaReplace); TrLo (&value[1], *value, "\015", nickExport->crReplace); TrLo (&value[1], *value, "\003", nickExport->crReplace); } theError = AccuAddStr (&nickExport->a, value); } } nickExport->theError = theError; return (false); } #ifdef VCARD // // AutoGeneratePersonalInformation // // Auto-generate personal information and add it into a given address book // as a nickname. // OSErr AutoGeneratePersonalInformation (short ab) { StringHandle hAddress; Handle expansion; Str255 returnAddress; Str31 realName, firstName, lastName, nickname; OSErr theError; theError = noErr; *returnAddress = 0; *firstName = 0; *lastName = 0; expansion = nil; // Grab some important vCard-ish information from the dominant personality GetDominantPref (PREF_REALNAME, realName); if (hAddress = NewString (GetShortReturnAddr (returnAddress))) { theError = SuckPtrAddresses (&expansion, LDRef (hAddress), GetHandleSize (hAddress), true, true, false, nil); ZapHandle (hAddress); } // Make it into a nickname if (!theError && *realName) { ParseFirstLast (realName, firstName, lastName); PSCopy (nickname, realName); BeautifyFrom (nickname); SanitizeFN (nickname, nickname, NICK_BAD_CHAR, NICK_REP_CHAR, false); *nickname = MIN (*nickname, sizeof (Str31) - 1); NewNickLow (expansion, CreateSimpleNotes (realName, firstName, lastName), 0, nickname, false, nrNone, true); } return (theError); } OSErr MakeVCardFile (FSSpec *spec, short ab, short nick) { FSSpec nickFolder, vcardFolder; Str255 vcardFolderName, nickname; Handle vcardData, notes; OSErr theError; long dirID; UseFeature (featureVCard); theError = noErr; // Make a vCard from this nickname if (notes = GetNicknameData (ab, nick, false, true)) Tr (notes, "\003", "\015"); if (vcardData = MakeVCard (GetNicknameData (ab, nick, true, true), notes)) { // Find the Nicknames Folder if (theError = SubFolderSpec (NICK_FOLDER, &nickFolder)) { SimpleMakeFSSpec (Root.vRef, Root.dirId, GetRString (nickFolder.name, NICK_FOLDER), &nickFolder); theError = FSpDirCreate (&nickFolder, smSystemScript, &dirID); } if (!theError) { IsAlias (&nickFolder, &nickFolder); theError = FSMakeFSSpec (nickFolder.vRefNum, SpecDirId (&nickFolder), GetRString (vcardFolderName, VCARD_FOLDER), &vcardFolder); // Create the vCards Folder if needed if (theError == fnfErr) theError = FSpDirCreate (&vcardFolder, smSystemScript, &dirID); } // Make an FSSpec for the vCard itself if (!theError) { IsAlias (&vcardFolder, &vcardFolder); theError = FSMakeFSSpec (vcardFolder.vRefNum, SpecDirId (&vcardFolder), MakeVCardFileName (ab, nick, nickname), spec); if (theError == fnfErr) { FSpCreateResFile (spec, CREATOR, VCARD_TYPE, smSystemScript); theError = ResError (); } } // Write the vCard data into the file if (!theError) { IsAlias (spec, spec); theError = Blat (spec, vcardData, false); } ZapHandle (vcardData); } else theError = MemError (); return (theError); } #endif #ifdef DEBUG void PaintUpdateRgn (WindowPtr theWindow, short color) { GrafPtr savePort; RGBColor oldColor; RgnHandle rgn; Point offsetPt; GetPort (&savePort); SetPort (GetWindowPort (theWindow)); rgn = MyGetWindowUpdateRegion(theWindow); GetForeColor (&oldColor); ForeColor (color); offsetPt.h = offsetPt.v = 0; GlobalToLocal (&offsetPt); OffsetRgn (rgn, offsetPt.h, offsetPt.v); Debugger (); PaintRgn (rgn); RGBForeColor (&oldColor); OffsetRgn(rgn, -offsetPt.h, -offsetPt.v); SetPort (savePort); } #endif /************************************************************************ * ABUnselectCurrentNickname - unselect the currently selected nickname ************************************************************************/ void ABUnselectCurrentNickname(void) { short ab, nick; Str31 massagedName; if (LVCountSelection (&NickList) == 1) { ab = AB.abDisplayed; nick = AB.nickDisplayed; if (ab != kAddressBookUndefined && nick != kNickUndefined) { GetListNameBasedOnTag (SortTag, ab, nick, massagedName); LVUnselect (&NickList, MakeNodeID (ab, nick), massagedName, true); } } } // // SortAddressBooks // // Sort regular address books. It is currently assumed that // these are in order. // void SortAddressBookList (void) { short totalAB, ab, firstRegular, lastRegular; firstRegular = kAddressBookUndefined; lastRegular = kAddressBookUndefined; totalAB = NAliases; ab = 0; while (ab < totalAB && firstRegular == kAddressBookUndefined) { if (IsRegularAddressBook (ab)) firstRegular = ab; ++ab; } ab = totalAB - 1; while (ab >= 0 && lastRegular == kAddressBookUndefined) { if (IsRegularAddressBook (ab)) lastRegular = ab; --ab; } if (ValidAddressBook (firstRegular) && ValidAddressBook (lastRegular) && firstRegular != lastRegular) { QuickSort (LDRef (Aliases), sizeof (AliasDesc), firstRegular, lastRegular, (void*) abCompare,(void*) abSwap); UL (Aliases); } } int abCompare (AliasDPtr adp1, AliasDPtr adp2) { return (StringComp (adp1->spec.name, adp2->spec.name)); } void abSwap (AliasDPtr adp1, AliasDPtr adp2) { AliasDesc temp = *adp2; *adp2 = *adp1; *adp1 = temp; } Boolean ABIsChattable(void) { short index = 1; short ab; short nick; Boolean retVal = false; Str31 aim, aim2; Str255 value; if (GetOSVersion() < 0x1030) return false; GetRString(aim,ABReservedTagsStrn+abTagAIM); GetRString(aim2,ABReservedTagsStrn+abTagAIM2); while (GetSelectedABNick (index++, &ab, &nick)) if (nick >= 0) { if (!(*GetTaggedFieldValueStr(ab, nick, aim, value) || *GetTaggedFieldValueStr(ab, nick, aim2, value))) return false; else retVal = true; } return retVal; } OSErr ABChat(short modifiers) { short ab; short nick; Boolean retVal = false; Str31 aim, aim2; Str255 value, url; GetRString(aim,ABReservedTagsStrn+abTagAIM); GetRString(aim2,ABReservedTagsStrn+abTagAIM2); if (GetSelectedABNick (1, &ab, &nick)) { if (*GetTaggedFieldValueStr(ab, nick, aim, value) || *GetTaggedFieldValueStr(ab, nick, aim2, value)) { ComposeRString(url,AIM_URL_FMT,URLEscape(value)); return OpenOtherURLPtr(GetRString(aim,AIM_PROTO),url+1,*url); } } return fnfErr; }