Map of Adium
This is a map to the internal structure of Adium. You'll need this if you want to write a plug-in or patch.
WARNING: THE FOLLOWING INFORMATION HAS NOT YET BEEN REVIEWED BY EVERY DEVELOPER. IT SHOULD NOT BE CONSIDERED FINAL NOR COMPLETE NOR CORRECT. PUPPIES MAY DIE IF YOU RELY ON THIS INFORMATION BEFORE THIS WARNING HAS BEEN REMOVED.
Creating a Plugin
There are a few pages about this:
- Development
- Spenser Jones' guide Building Your First Adium Plugin
- Toby Peterson's guide
- Patrick's page
First principles
Most access to the internals of Adium is done through AIAdium's assortment of controllers and handy methods. Anything using Adium's precompiled header automatically gets access to the global variable
id<AIAdium> adium;
If your code doesn't get automatic access (for example if it's in an external plugin), you can do the following to get access:
#import <Adium/AISharedAdium.h>
Notifications
Much of the information that comes to you from elsewhere in Adium will arrive by means of a notification through the local notification center.
- NSObject
- NSNotificationCenter
The notification names are listed in <Adium/AIAdiumProtocol.h>, as well as here.
In the 1.3 series and earlier versions, Adium used a separate notification center owned by and accessed through the AIAdium object. We removed this for Adium 1.4 and later.
Contacts
- NSObject
- ESObjectWithStatus
- AIListObject
- AIListContact
- AIListObject
- AIContactController <AIContactController>
- ESObjectWithStatus
- [adium contactController]
Creating a new contact from an account+UID
Call [contactController contactWithService:service account:account UID:UID]. This method creates a new contact if a matching one didn't previously exist. Note that this contact will not show up anywhere as long as it's not in a group.
Adding a contact to the contact list
See Groups/Adding a contact to a group.
Removing a contact from the contact list
Call [contactController removeListObjects:[NSArray arrayWithObject:contactToRemove]]. Adium does not delete data associated with the contact; chat transcripts remain on the hard drive, and settings (such as alias or notes) will be present but unused such that if the user adds the contact back the information will be 'restored'.
Finding what group a contact is in
Call [contactController remoteGroupForContact:contact]. [contact containingObject] won't always work; for example, the contact may be within a metacontact. [contact parentContact] returns the outermost contact, so if the contact is within a metacontact, the metacontact will be returned, but if it isn't self will be returned.
Sending messages or anything else to a contact
XXX - do we really follow this process when sending? I think the core handles determination of the preferred account -evands
First, call [accountController preferredAccountForSendingContentType: CONTENT_FOO_TYPE toContact:destContact]. Content types are:
- CONTENT_CONTEXT_TYPE
- CONTENT_EVENT_TYPE
- CONTENT_FILE_TRANSFER_TYPE
- CONTENT_MESSAGE_TYPE
- CONTENT_STATUS_TYPE
- CONTENT_TYPING_TYPE
Most of the time, you'll use CONTENT_MESSAGE_TYPE.
Next, create a chat. See Chats/Creating a chat with a contact.
Next, create a content object. For a message, use [[AIContentMessage alloc] initWithChat:chat source:XXX destination:destContact date:[NSDate date] message:messageAttrStr]. For anything else, use [[contentObjectClass alloc] initWithChat:chat source:XXX destination:destContact date:[NSDate date]]. Content object classes are:
- AIContentContext
- AIContentEvent
- AIContentMessage
- AIContentStatus
- AIContentTyping
- ESFileTransfer
Finally, send the content object with [contentController sendContentObject:contentObject]. This returns a BOOL indicating whether it was successfully sent.
Sending raw text to a contact
First, call [accountController preferredAccountForSendingContentType: CONTENT_MESSAGE_TYPE toContact:contact]. Then, call [contentController sendRawMessage:messageString toContact:contact].
This should not be used for sending messages to users, generally. The only place it is currently used within Adium is for sending OTR encryption messages which are postprocessed on the other side and therefore never displayed. This will not give any visual indication to the local user that a message was sent.
Storing Anything Along With a Contact
For runtime data: [contact setStatusObject:forKey:notify:] and [contact statusObjectForKey:] For permanent data: [contact setPreference:forKey:group:] and [contact preferenceForKey:group:]
Metacontacts
NOTE: Metacontacts are horribly broken and have to be fixed/rewritten for a future version, so this might change.
- NSObject
- ESObjectWithStatus
- AIListObject
- AIListContact
- AIMetaContact <AIContainingObject>
- AIListContact
- AIListObject
- AIContactController <AIContactController>
- ESObjectWithStatus
- [adium contactController]
Creating a new metacontact
Call [contactController groupListContacts:contactsToGroup]. This returns the created AIMetaContact. XXX Is there anything to do after this? For example, putting the metacontact in a group?
Getting an array of every contact in the metacontact
Call [metacontact containedObjects] or [metacontact listObjects].
Adding contacts to the metacontact
Call [contactController moveContact:contactToMove intoObject:metacontact]. If you want to specify the position within the metacontact (e.g. drag-and-drop), or move multiple objects at a time, use [contactController moveListObjects:[NSArray arrayWithObject:contactToMove] intoObject:metacontact index:desiredIndex].
Removing contacts from the metacontact
Call [contactController removeAllListObjectsMatching:contactToRemove fromMetaContact:metaContact].
Finding what group a metacontact is in
Call [contactController remoteGroupForContact:metacontact]. [metacontact parentObject] should work as well, but the former is better for consistency.
Groups
NOTE: Since Metacontacts are horribly broken right now and have to be fixed/rewritten for a future version, this might change a bit.
- NSObject
- ESObjectWithStatus
- AIListObject
- AIListGroup <AIContainingObject>
- AIListObject
- AIContactController <AIContactController>
- ESObjectWithStatus
- [adium contactController]
Getting the root group
Call [contactController contactList].
Creating a group
Call [contactController groupWithUID:groupName].
Deleting a group
Call [contactController removeListObjects:[NSArray arrayWithObject:groupToRemove]].
Getting an array of every contact in the group
Call [group containedObjects]. That array may contain duplicates (e.g. on different accounts without being in a metacontact); for a duplicate-free array, use [group listObjects].
Adding a contact to a group
Call [contactController addContacts:[NSArray arrayWithObject:contactToAdd] toGroup:group].
Moving a contact from one group to another
Call [contactController moveContact:contactToMove intoObject:group]. If you want to specify the position within the group (e.g. drag-and-drop), or move multiple objects at a time, use [contactController moveListObjects:[NSArray arrayWithObject:contactToMove] intoObject:group index:desiredIndex].
Removing a contact from a group
This is equivalent to removing them from the contact list, so call [contactController removeListObjects:[NSArray arrayWithObject:contactToRemove]]. XXX Safe to remove them while they are in a metacontact?
Services
- NSObject
- AIService
- <service-specific subclass>
- AIService
Adding a new service
- Subclass AIService.
- Instantiate the subclass.
- Call [accountController registerService:service].
This is best done from a plug-in (in -installPlugin).
Subclassing AIService
XXX
Accounts
- NSObject
- AIAccount (Note: Some methods are in AIAbstractAccount.[hm], but they are categoried onto AIAccount)
- <service-specific subclass>
- AIAccountController <AIAccountController>
- AIAccount (Note: Some methods are in AIAbstractAccount.[hm], but they are categoried onto AIAccount)
Adding a new account
Call [accountController createAccountWithService:service UID:UID]. This returns an AIAccount. Then call [accountController addAccount:account].
Removing an account
Call [accountController deleteAccount:account].
Chats
AIChatController handles almost everything chat related; Creating new single-user & group chats, returning the approriate chat for a given indentifier type, the addition of contacts to a chat, etc.
- NSObject
- ESObjectWithStatus
- AIChat <AIContainingObject>
- AIChatController <AIChatController>
- ESObjectWithStatus
- To call methods in AIChatController from any part of adium, you can use: [[adium chatController] methodName]
Creating a chat with a contact
To create a chat with a contact (ie. a "single user chat"), call [AIChatController chatWithContact:contactName]. This method takes an AIListContact as a parameters, and returns an AIChat, which is the newly created chat.
Finding all chats that involve a contact
To find all chats containing (involving) a contact, call [AIChatController existingChatWithContact:contact] This method takes an AIListContact as a parameter, and returns the first chat with the contact that is found. If no chats are found, the method returns nil.
Creating a group chat
To open a group chat, call [AIChatController chatWithName:name identifier:anIdentifier onAccount:account creationInfo:inInfo] The parameters to this method are:
- name - an NSString object representing the name of the chat
- identifier - an object of type id by which the contactController can search for an existing chat. If this is not given, the contactController will attempt
to find the chat by searching by name.
- account - the account to open the chat on
- creationInfo - A dictionary of information which may be used by the account when joining the chat serverside
Inviting a contact to a group chat
XXX
Receiving messages from a chat
XXX
Sending a message to a chat
XXX
Sending an event to a chat
XXX
Preferences
- NSObject
- AIPreferenceController <AIPreferenceController>
- AIModularPane
- AIPreferencePane
- <plug-in-specific subclass>
- AIPreferencePane
- [adium preferenceController]
Preferences exist either globally or by-object. For example, all the settings that you see in the Account Editor sheet are by-object on the account object being edited.
Adding a new preferences group
XXX
Observing preference changes
XXX
Opening the Preferences window to a specific category
Call [preferenceController openPreferencesToCategory:], passing any of:
- AIPref_General
- AIPref_Accounts
- AIPref_Personal
- AIPref_Appearance
- AIPref_Messages
- AIPref_Status
- AIPref_Events
- AIPref_FileTransfer
XXX The above method does not exist! I made it up. But it should, since there is no defined list of identifier strings. Alternatively, we can define such a list. —Peter
If you want a pane in the Advanced category, use -openPreferencesToAdvancedPane: and pass the localized name of the pane you want to open it to.
Adding a category to the Preferences window
You'll need to create a subclass of AIPreferencePane, and then instantiate that subclass, and then add that pane using [preferenceController addPreferencePane:].
Custom panes go in the Advanced category. It is theoretically possible to override -category and return some other category, but your pane will not be visible. The window is not wide enough, and we don't modify the toolbar anyway.
In your subclass, you'll need to override -label (localized display name), -image (display icon) and -nibName (for loading the view).
You should be able to use Cocoa Bindings to store the values of all your preference controls. If this doesn't work and you need to work around it, or you simply haven't learned Bindings yet, use the -changePreference: action. You'll need to override it, obviously, but you should also call super.
When your pane is displayed, or when -changePreference: is received, -configureControlDimming is sent to it. If you're not using Bindings for everything (you're mixing and matching or not using Bindings at all), you should enable/disable your controls here. If you are using Bindings for everything, you can skip overriding this method.
Plug-ins
- NSObject
- AIPlugin <AIPlugin, AIPluginInfo>
- <subclass>
- AIPlugin <AIPlugin, AIPluginInfo>
Anatomy of a .AdiumPlugin bundle
You need these in your Info.plist:
- CFBundleSignature: AdiM
- NSPrincipalClass: <name of your subclass of AIPlugin>
When your plug-in is loaded, Adium will call [instanceOfYourPrincipalClass installPlugin]. When your plug-in is unloaded, Adium will call [instanceOfYourPrincipalClass uninstallPlugin].
Doing things when your plug-in is loaded
Override -(void)installPlugin.
Doing things when your plug-in is uninstalled
Override -(void)uninstallPlugin.
Supplying information about your plug-in
Override -pluginAuthor, -pluginVersion, -pluginDescription, and -pluginURL. Each of these returns an NSString (yes, including -pluginURL).