From 1355f099f7c9b800fe305e04ae4fbe89e440c0d7 Mon Sep 17 00:00:00 2001 From: Joseph Spiros Date: Sun, 1 Mar 2009 03:10:43 -0500 Subject: [PATCH] Initial commit of GrowlITTSW. Currently, all settings are hardcoded. --- .gitignore | 3 + English.lproj/GrowlITTSWPrefs.nib/classes.nib | 73 +++ English.lproj/GrowlITTSWPrefs.nib/info.nib | 20 + .../GrowlITTSWPrefs.nib/keyedobjects.nib | Bin 0 -> 2072 bytes English.lproj/InfoPlist.strings | Bin 0 -> 92 bytes GrowlApplicationNotification.h | 72 +++ GrowlDefines.h | 348 +++++++++++++ GrowlDefinesInternal.h | 430 ++++++++++++++++ GrowlDisplayPlugin.h | 75 +++ GrowlITTSW.xcodeproj/TemplateIcon.icns | Bin 0 -> 52318 bytes GrowlITTSW.xcodeproj/project.pbxproj | 468 ++++++++++++++++++ GrowlITTSWController.h | 19 + GrowlITTSWController.m | 93 ++++ GrowlITTSWDisplay.h | 11 + GrowlITTSWDisplay.m | 42 ++ GrowlITTSWPrefs.h | 6 + GrowlITTSWPrefs.m | 6 + GrowlITTSWWindow.h | 15 + GrowlITTSWWindow.m | 252 ++++++++++ GrowlITTSW_Prefix.pch | 7 + GrowlPlugin.h | 105 ++++ Info.plist | 32 ++ 22 files changed, 2077 insertions(+) create mode 100644 .gitignore create mode 100644 English.lproj/GrowlITTSWPrefs.nib/classes.nib create mode 100644 English.lproj/GrowlITTSWPrefs.nib/info.nib create mode 100644 English.lproj/GrowlITTSWPrefs.nib/keyedobjects.nib create mode 100644 English.lproj/InfoPlist.strings create mode 100644 GrowlApplicationNotification.h create mode 100644 GrowlDefines.h create mode 100644 GrowlDefinesInternal.h create mode 100644 GrowlDisplayPlugin.h create mode 100644 GrowlITTSW.xcodeproj/TemplateIcon.icns create mode 100644 GrowlITTSW.xcodeproj/project.pbxproj create mode 100644 GrowlITTSWController.h create mode 100644 GrowlITTSWController.m create mode 100644 GrowlITTSWDisplay.h create mode 100644 GrowlITTSWDisplay.m create mode 100644 GrowlITTSWPrefs.h create mode 100644 GrowlITTSWPrefs.m create mode 100644 GrowlITTSWWindow.h create mode 100644 GrowlITTSWWindow.m create mode 100644 GrowlITTSW_Prefix.pch create mode 100644 GrowlPlugin.h create mode 100644 Info.plist diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ab810ff --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +*.pbxuser +*.mode1v3 +.DS_Store diff --git a/English.lproj/GrowlITTSWPrefs.nib/classes.nib b/English.lproj/GrowlITTSWPrefs.nib/classes.nib new file mode 100644 index 0000000..1f69d3a --- /dev/null +++ b/English.lproj/GrowlITTSWPrefs.nib/classes.nib @@ -0,0 +1,73 @@ + + + + + IBClasses + + + CLASS + NSView + LANGUAGE + ObjC + SUPERCLASS + NSResponder + + + CLASS + FirstResponder + LANGUAGE + ObjC + SUPERCLASS + NSObject + + + CLASS + NSObject + LANGUAGE + ObjC + + + CLASS + NSPreferencePane + LANGUAGE + ObjC + OUTLETS + + _firstKeyView + NSView + _initialKeyView + NSView + _lastKeyView + NSView + _window + NSWindow + + SUPERCLASS + NSObject + + + CLASS + NSWindow + LANGUAGE + ObjC + SUPERCLASS + NSResponder + + + CLASS + GrowlITTSWPrefs + LANGUAGE + ObjC + OUTLETS + + slider_opacity + NSSlider + + SUPERCLASS + NSPreferencePane + + + IBVersion + 1 + + diff --git a/English.lproj/GrowlITTSWPrefs.nib/info.nib b/English.lproj/GrowlITTSWPrefs.nib/info.nib new file mode 100644 index 0000000..5155f4a --- /dev/null +++ b/English.lproj/GrowlITTSWPrefs.nib/info.nib @@ -0,0 +1,20 @@ + + + + + IBFramework Version + 672 + IBLastKnownRelativeProjectPath + ../../../../Growl.xcodeproj + IBOldestOS + 3 + IBOpenObjects + + 6 + + IBSystem Version + 9G55 + targetFramework + IBCocoaFramework + + diff --git a/English.lproj/GrowlITTSWPrefs.nib/keyedobjects.nib b/English.lproj/GrowlITTSWPrefs.nib/keyedobjects.nib new file mode 100644 index 0000000000000000000000000000000000000000..8994fc5e7a70d37f4ffc7cced1727c051f237a30 GIT binary patch literal 2072 zcmZ`(X>b%p6rS#$HynXwk|2bj>;j8`gt*xR2#KOeFc35wWpWV`;$$<1fz8Z1GfN0d zv{4c9K!k`_sgQV-RhIWvcoY_v7orjrrMSwXEbsfieY3NP{?Prg-TnH#?|tv<_ogi# z(`_dddWTRZf;3`eV{naT*}7@e1|2iLET~%1l{%UlgJ#<*E$Y}=0=atUfnmd2s><^{ zQ@iS@oV3bgBFgz%x7J>k4tZEE${1BJXYN^Nmz`I2k-o~X>@^u1GOoCK(>0r~zu~5v z@3`ykUH3oK`Q%eiKlAJhFTeTL+Xn^?9y)xqE@&iTu@iFEXx|t)Gb?-SDdWcHOvs&h z>S>ewfyq;*2B)1qea6f)X64P!KQmMxQI?vfQy+}RRNHQeC^a5SBx0u+Rk(J|u2*9T z4J{KQN?A0j*|y%M$8@K=MsH8wVQhtI7+4|}Z!eE15w%mZT^!t3T6pL(t<|oVhv?ok zVLk+Kw|J2X_`gJHY}0Z*D^0_xRt>d7vsz_2qSWiiC#E&%M!VVN@lMw<2si#ec)Mwr zVOnjigDjdoE?xkqypW&DXYeo|&L?x=)A>w3g@?GnB_7}f@@{^{fG=EFgtevWak+Dm zGt_L6Wp>4?YHO8-3oY#udthGi{E|jbU(IgvHU_%)m6Wc)=!ETrs{2HkOfeHyRBMPR7pQGojOX%^SoegZOMOahU4292vZm$DEh}0t zR@iPL5W(LW-P@QM`Z7&R(E0=YT8g+8B;slr}FG6jfUdg5Ok_m z*DCrtE!Ae8Te%utts5P!a^_&5;&jKfYSmut`s;@4kCM9tQjlEcIs6npDl5KL5V((L z@bNO0$MA7{OskxeOcu;5%nKCGncI^WNEXg1$_s>x=lAps_*Ta{5is9&EKJ8o*L&$v zP&3{izqB+(X;9LXkE_<;Chd$M2|l{k<#MRYx3=3_mt5a-S(2xM-KJWq*>Tfo$Jvi4 zYf^d)PC~j2B@x%GH5kt``PfV%@GRV9H~qsD30-+r|5Y1HaZKGj7(WEsDvwJhLwSLa zTU){0V%!#l!o@v3P04TpR$UOD-_t7RCJXb6&>Yrg6&9lu{~US{{MzgKuESz+g1nk) zwa$1iw?fu((-M&t%S7upi zH+EQR#irbG3-|X&Zfn6-JB`ck2;GTeGYK1|ERLVjXsR_4XJ>C;Z>j6wkN$pt1RIm` zk*?tZ?!N~C{2l_QuI&-DJ^I+=Pw=Tc$fxnsm;Vc_8RdzxMjRH}>;cJde-j`TR^7vj6gaf5?(d4wAyXh|4 zPamYa>0Y{z?x%0j1N0#Moc_WjHkM6dd2BwbV0BDqHoKf{U{|t@Y!kblZDCv4HrC5_ zuv^*f>`Atpy~JK-``G|H#6D)WD&Ra=1a;5^Ezkz- zuo6~53=FU#0qbBrTm~GjfK9L&w!l`{2EDKY`ru)B4qk-4@DjWR```^Y00-eP9D!qS z0zQC~@G*P}U&FWXJ^Tni!>{nW1kx~Rgp?u4QkIl0(rkxKpsW>dU< literal 0 HcmV?d00001 diff --git a/English.lproj/InfoPlist.strings b/English.lproj/InfoPlist.strings new file mode 100644 index 0000000000000000000000000000000000000000..5e45963c382ba690b781b953a00585212b898ac5 GIT binary patch literal 92 zcmW-XQ3`+{5C!MkQ~2$No+IcIkqMDxWCV8j>LCj|yTg2Mz+o9F%uHlf9u}h9EuK`F a!Y*1dX%G66ZqL#C$|bw0ZoP5@jOGW1ArT7z literal 0 HcmV?d00001 diff --git a/GrowlApplicationNotification.h b/GrowlApplicationNotification.h new file mode 100644 index 0000000..2c99ef5 --- /dev/null +++ b/GrowlApplicationNotification.h @@ -0,0 +1,72 @@ +// +// GrowlApplicationNotification.h +// Growl +// +// Created by Mac-arena the Bored Zo on 2005-07-31. +// Copyright 2005-2006 The Growl Project. All rights reserved. +// + +@interface GrowlApplicationNotification: NSObject +{ + NSString *name, *applicationName; + NSString *title, *description; + NSAttributedString *attributedTitle, *attributedDescription; + + NSDictionary *dictionary, *auxiliaryDictionary; + + unsigned GANReserved: 30; +} + ++ (GrowlApplicationNotification *) notificationWithDictionary:(NSDictionary *)dict; + +- (GrowlApplicationNotification *) initWithDictionary:(NSDictionary *)dict; + +//you can pass nil for description. +- (GrowlApplicationNotification *) initWithName:(NSString *)newName + applicationName:(NSString *)newAppName + title:(NSString *)newTitle + description:(NSString *)newDesc; + +//you can pass nil for description. +- (GrowlApplicationNotification *) initWithName:(NSString *)newName + applicationName:(NSString *)newAppName + title:(NSString *)newTitle + description:(NSString *)newDesc; + +#pragma mark - + +/*as of 0.8, this returns: + * * GROWL_NOTIFICATION_NAME + * * GROWL_APP_NAME + * * GROWL_NOTIFICATION_TITLE + * * GROWL_NOTIFICATION_DESCRIPTION + *you can pass this set to -dictionaryRepresentationWithKeys:. + */ ++ (NSSet *) standardKeys; + +//same as dictionaryRepresentationWithKeys:nil. +- (NSDictionary *) dictionaryRepresentation; + +/*with nil, returns all of the standard keys plus the auxiliary dictionary. + *with non-nil, returns only the keys (from internal storage plus the auxiliary + * dictionary) that are in the set. + *in other words, returns the intersection of the standard dictionary keys, the + * auxiliary dictionary, and the provided keys. + */ +- (NSDictionary *) dictionaryRepresentationWithKeys:(NSSet *)keys; + +#pragma mark - + +- (NSString *) name; +- (NSString *) applicationName; + +- (NSString *) title; +- (NSAttributedString *) attributedTitle; + +- (NSString *) notificationDescription; +- (NSAttributedString *) attributedDescription; + +- (NSDictionary *) auxiliaryDictionary; +- (void) setAuxiliaryDictionary:(NSDictionary *)newAuxDict; + +@end diff --git a/GrowlDefines.h b/GrowlDefines.h new file mode 100644 index 0000000..2b971cf --- /dev/null +++ b/GrowlDefines.h @@ -0,0 +1,348 @@ +// +// GrowlDefines.h +// + +#ifndef _GROWLDEFINES_H +#define _GROWLDEFINES_H + +#ifdef __OBJC__ +#define XSTR(x) (@x) +#define STRING_TYPE NSString * +#else +#define XSTR CFSTR +#define STRING_TYPE CFStringRef +#endif + +/*! @header GrowlDefines.h + * @abstract Defines all the notification keys. + * @discussion Defines all the keys used for registration with Growl and for + * Growl notifications. + * + * Most applications should use the functions or methods of Growl.framework + * instead of posting notifications such as those described here. + * @updated 2004-01-25 + */ + +// UserInfo Keys for Registration +#pragma mark UserInfo Keys for Registration + +/*! @group Registration userInfo keys */ +/* @abstract Keys for the userInfo dictionary of a GROWL_APP_REGISTRATION distributed notification. + * @discussion The values of these keys describe the application and the + * notifications it may post. + * + * Your application must register with Growl before it can post Growl + * notifications (and have them not be ignored). However, as of Growl 0.6, + * posting GROWL_APP_REGISTRATION notifications directly is no longer the + * preferred way to register your application. Your application should instead + * use Growl.framework's delegate system. + * See +[GrowlApplicationBridge setGrowlDelegate:] or Growl_SetDelegate for + * more information. + */ + +/*! @defined GROWL_APP_NAME + * @abstract The name of your application. + * @discussion The name of your application. This should remain stable between + * different versions and incarnations of your application. + * For example, "SurfWriter" is a good app name, whereas "SurfWriter 2.0" and + * "SurfWriter Lite" are not. + */ +#define GROWL_APP_NAME XSTR("ApplicationName") +/*! @defined GROWL_APP_ID + * @abstract The bundle identifier of your application. + * @discussion The bundle identifier of your application. This key should + * be unique for your application while there may be several applications + * with the same GROWL_APP_NAME. + * This key is optional. + */ +#define GROWL_APP_ID XSTR("ApplicationId") +/*! @defined GROWL_APP_ICON + * @abstract The image data for your application's icon. + * @discussion Image data representing your application's icon. This may be + * superimposed on a notification icon as a badge, used as the notification + * icon when a notification-specific icon is not supplied, or ignored + * altogether, depending on the display. Must be in a format supported by + * NSImage, such as TIFF, PNG, GIF, JPEG, BMP, PICT, or PDF. + * + * Optional. Not supported by all display plugins. + */ +#define GROWL_APP_ICON XSTR("ApplicationIcon") +/*! @defined GROWL_NOTIFICATIONS_DEFAULT + * @abstract The array of notifications to turn on by default. + * @discussion These are the names of the notifications that should be enabled + * by default when your application registers for the first time. If your + * application reregisters, Growl will look here for any new notification + * names found in GROWL_NOTIFICATIONS_ALL, but ignore any others. + */ +#define GROWL_NOTIFICATIONS_DEFAULT XSTR("DefaultNotifications") +/*! @defined GROWL_NOTIFICATIONS_ALL + * @abstract The array of all notifications your application can send. + * @discussion These are the names of all of the notifications that your + * application may post. See GROWL_NOTIFICATION_NAME for a discussion of good + * notification names. + */ +#define GROWL_NOTIFICATIONS_ALL XSTR("AllNotifications") +/*! @defined GROWL_NOTIFICATIONS_HUMAN_READABLE_DESCRIPTIONS + * @abstract A dictionary of human-readable names for your notifications. + * @discussion By default, the Growl UI will display notifications by the names given in GROWL_NOTIFICATIONS_ALL + * which correspond to the GROWL_NOTIFICATION_NAME. This dictionary specifies the human-readable name to display. + * The keys of the dictionary are GROWL_NOTIFICATION_NAME strings; the objects are the human-readable versions. + * For any GROWL_NOTIFICATION_NAME not specific in this dictionary, the GROWL_NOTIFICATION_NAME will be displayed. + * + * This key is optional. + */ +#define GROWL_NOTIFICATIONS_HUMAN_READABLE_NAMES XSTR("HumanReadableNames") +/*! @defined GROWL_NOTIFICATIONS_DESCRIPTIONS +* @abstract A dictionary of descriptions of _when_ each notification occurs +* @discussion This is an NSDictionary whose keys are GROWL_NOTIFICATION_NAME strings and whose objects are +* descriptions of _when_ each notification occurs, such as "You received a new mail message" or +* "A file finished downloading". +* +* This key is optional. +*/ +#define GROWL_NOTIFICATIONS_DESCRIPTIONS XSTR("NotificationDescriptions") + +/*! @defined GROWL_TICKET_VERSION + * @abstract The version of your registration ticket. + * @discussion Include this key in a ticket plist file that you put in your + * application bundle for auto-discovery. The current ticket version is 1. + */ +#define GROWL_TICKET_VERSION XSTR("TicketVersion") +// UserInfo Keys for Notifications +#pragma mark UserInfo Keys for Notifications + +/*! @group Notification userInfo keys */ +/* @abstract Keys for the userInfo dictionary of a GROWL_NOTIFICATION distributed notification. + * @discussion The values of these keys describe the content of a Growl + * notification. + * + * Not all of these keys are supported by all displays. Only the name, title, + * and description of a notification are universal. Most of the built-in + * displays do support all of these keys, and most other visual displays + * probably will also. But, as of 0.6, the Log, MailMe, and Speech displays + * support only textual data. + */ + +/*! @defined GROWL_NOTIFICATION_NAME + * @abstract The name of the notification. + * @discussion The name of the notification. Note that if you do not define + * GROWL_NOTIFICATIONS_HUMAN_READABLE_NAMES when registering your ticket originally this name + * will the one displayed within the Growl preference pane and should be human-readable. + */ +#define GROWL_NOTIFICATION_NAME XSTR("NotificationName") +/*! @defined GROWL_NOTIFICATION_TITLE + * @abstract The title to display in the notification. + * @discussion The title of the notification. Should be very brief. + * The title usually says what happened, e.g. "Download complete". + */ +#define GROWL_NOTIFICATION_TITLE XSTR("NotificationTitle") +/*! @defined GROWL_NOTIFICATION_DESCRIPTION + * @abstract The description to display in the notification. + * @discussion The description should be longer and more verbose than the title. + * The description usually tells the subject of the action, + * e.g. "Growl-0.6.dmg downloaded in 5.02 minutes". + */ +#define GROWL_NOTIFICATION_DESCRIPTION XSTR("NotificationDescription") +/*! @defined GROWL_NOTIFICATION_ICON + * @discussion Image data for the notification icon. Must be in a format + * supported by NSImage, such as TIFF, PNG, GIF, JPEG, BMP, PICT, or PDF. + * + * Optional. Not supported by all display plugins. + */ +#define GROWL_NOTIFICATION_ICON XSTR("NotificationIcon") +/*! @defined GROWL_NOTIFICATION_APP_ICON + * @discussion Image data for the application icon, in case GROWL_APP_ICON does + * not apply for some reason. Must be in a format supported by NSImage, such + * as TIFF, PNG, GIF, JPEG, BMP, PICT, or PDF. + * + * Optional. Not supported by all display plugins. + */ +#define GROWL_NOTIFICATION_APP_ICON XSTR("NotificationAppIcon") +/*! @defined GROWL_NOTIFICATION_PRIORITY + * @discussion The priority of the notification as an integer number from + * -2 to +2 (+2 being highest). + * + * Optional. Not supported by all display plugins. + */ +#define GROWL_NOTIFICATION_PRIORITY XSTR("NotificationPriority") +/*! @defined GROWL_NOTIFICATION_STICKY + * @discussion A Boolean number controlling whether the notification is sticky. + * + * Optional. Not supported by all display plugins. + */ +#define GROWL_NOTIFICATION_STICKY XSTR("NotificationSticky") +/*! @defined GROWL_NOTIFICATION_CLICK_CONTEXT + * @abstract Identifies which notification was clicked. + * @discussion An identifier for the notification for clicking purposes. + * + * This will be passed back to the application when the notification is + * clicked. It must be plist-encodable (a data, dictionary, array, number, or + * string object), and it should be unique for each notification you post. + * A good click context would be a UUID string returned by NSProcessInfo or + * CFUUID. + * + * Optional. Not supported by all display plugins. + */ +#define GROWL_NOTIFICATION_CLICK_CONTEXT XSTR("NotificationClickContext") + +/*! @defined GROWL_DISPLAY_PLUGIN + * @discussion The name of a display plugin which should be used for this notification. + * Optional. If this key is not set or the specified display plugin does not + * exist, the display plugin stored in the application ticket is used. This key + * allows applications to use different default display plugins for their + * notifications. The user can still override those settings in the preference + * pane. + */ +#define GROWL_DISPLAY_PLUGIN XSTR("NotificationDisplayPlugin") + +/*! @defined GROWL_NOTIFICATION_IDENTIFIER + * @abstract An identifier for the notification for coalescing purposes. + * Notifications with the same identifier fall into the same class; only + * the last notification of a class is displayed on the screen. If a + * notification of the same class is currently being displayed, it is + * replaced by this notification. + * + * Optional. Not supported by all display plugins. + */ +#define GROWL_NOTIFICATION_IDENTIFIER XSTR("GrowlNotificationIdentifier") + +/*! @defined GROWL_APP_PID + * @abstract The process identifier of the process which sends this + * notification. If this field is set, the application will only receive + * clicked and timed out notifications which originate from this process. + * + * Optional. + */ +#define GROWL_APP_PID XSTR("ApplicationPID") + +/*! @defined GROWL_NOTIFICATION_PROGRESS +* @abstract If this key is set, it should contain a double value wrapped +* in a NSNumber which describes some sort of progress (from 0.0 to 100.0). +* If this is key is not set, no progress bar is shown. +* +* Optional. Not supported by all display plugins. +*/ +#define GROWL_NOTIFICATION_PROGRESS XSTR("NotificationProgress") + +// Notifications +#pragma mark Notifications + +/*! @group Notification names */ +/* @abstract Names of distributed notifications used by Growl. + * @discussion These are notifications used by applications (directly or + * indirectly) to interact with Growl, and by Growl for interaction between + * its components. + * + * Most of these should no longer be used in Growl 0.6 and later, in favor of + * Growl.framework's GrowlApplicationBridge APIs. + */ + +/*! @defined GROWL_APP_REGISTRATION + * @abstract The distributed notification for registering your application. + * @discussion This is the name of the distributed notification that can be + * used to register applications with Growl. + * + * The userInfo dictionary for this notification can contain these keys: + *
    + *
  • GROWL_APP_NAME
  • + *
  • GROWL_APP_ICON
  • + *
  • GROWL_NOTIFICATIONS_ALL
  • + *
  • GROWL_NOTIFICATIONS_DEFAULT
  • + *
+ * + * No longer recommended as of Growl 0.6. An alternate method of registering + * is to use Growl.framework's delegate system. + * See +[GrowlApplicationBridge setGrowlDelegate:] or Growl_SetDelegate for + * more information. + */ +#define GROWL_APP_REGISTRATION XSTR("GrowlApplicationRegistrationNotification") +/*! @defined GROWL_APP_REGISTRATION_CONF + * @abstract The distributed notification for confirming registration. + * @discussion The name of the distributed notification sent to confirm the + * registration. Used by the Growl preference pane. Your application probably + * does not need to use this notification. + */ +#define GROWL_APP_REGISTRATION_CONF XSTR("GrowlApplicationRegistrationConfirmationNotification") +/*! @defined GROWL_NOTIFICATION + * @abstract The distributed notification for Growl notifications. + * @discussion This is what it all comes down to. This is the name of the + * distributed notification that your application posts to actually send a + * Growl notification. + * + * The userInfo dictionary for this notification can contain these keys: + *
    + *
  • GROWL_NOTIFICATION_NAME (required)
  • + *
  • GROWL_NOTIFICATION_TITLE (required)
  • + *
  • GROWL_NOTIFICATION_DESCRIPTION (required)
  • + *
  • GROWL_NOTIFICATION_ICON
  • + *
  • GROWL_NOTIFICATION_APP_ICON
  • + *
  • GROWL_NOTIFICATION_PRIORITY
  • + *
  • GROWL_NOTIFICATION_STICKY
  • + *
  • GROWL_NOTIFICATION_CLICK_CONTEXT
  • + *
  • GROWL_APP_NAME (required)
  • + *
+ * + * No longer recommended as of Growl 0.6. Three alternate methods of posting + * notifications are +[GrowlApplicationBridge notifyWithTitle:description:notificationName:iconData:priority:isSticky:clickContext:], + * Growl_NotifyWithTitleDescriptionNameIconPriorityStickyClickContext, and + * Growl_PostNotification. + */ +#define GROWL_NOTIFICATION XSTR("GrowlNotification") +/*! @defined GROWL_SHUTDOWN +* @abstract The distributed notification name that tells Growl to shutdown. +* @discussion The Growl preference pane posts this notification when the +* "Stop Growl" button is clicked. +*/ +#define GROWL_SHUTDOWN XSTR("GrowlShutdown") +/*! @defined GROWL_PING + * @abstract A distributed notification to check whether Growl is running. + * @discussion This is used by the Growl preference pane. If it receives a + * GROWL_PONG, the preference pane takes this to mean that Growl is running. + */ +#define GROWL_PING XSTR("Honey, Mind Taking Out The Trash") +/*! @defined GROWL_PONG + * @abstract The distributed notification sent in reply to GROWL_PING. + * @discussion GrowlHelperApp posts this in reply to GROWL_PING. + */ +#define GROWL_PONG XSTR("What Do You Want From Me, Woman") +/*! @defined GROWL_IS_READY + * @abstract The distributed notification sent when Growl starts up. + * @discussion GrowlHelperApp posts this when it has begin listening on all of + * its sources for new notifications. GrowlApplicationBridge (in + * Growl.framework), upon receiving this notification, reregisters using the + * registration dictionary supplied by its delegate. + */ +#define GROWL_IS_READY XSTR("Lend Me Some Sugar; I Am Your Neighbor!") +/*! @defined GROWL_NOTIFICATION_CLICKED + * @abstract The distributed notification sent when a supported notification is clicked. + * @discussion When a Growl notification with a click context is clicked on by + * the user, Growl posts this distributed notification. + * The GrowlApplicationBridge responds to this notification by calling a + * callback in its delegate. + */ +#define GROWL_NOTIFICATION_CLICKED XSTR("GrowlClicked!") +#define GROWL_NOTIFICATION_TIMED_OUT XSTR("GrowlTimedOut!") + +/*! @group Other symbols */ +/* Symbols which don't fit into any of the other categories. */ + +/*! @defined GROWL_KEY_CLICKED_CONTEXT + * @abstract Used internally as the key for the clickedContext passed over DNC. + * @discussion This key is used in GROWL_NOTIFICATION_CLICKED, and contains the + * click context that was supplied in the original notification. + */ +#define GROWL_KEY_CLICKED_CONTEXT XSTR("ClickedContext") +/*! @defined GROWL_REG_DICT_EXTENSION + * @abstract The filename extension for registration dictionaries. + * @discussion The GrowlApplicationBridge in Growl.framework registers with + * Growl by creating a file with the extension of .(GROWL_REG_DICT_EXTENSION) + * and opening it in the GrowlHelperApp. This happens whether or not Growl is + * running; if it was stopped, it quits immediately without listening for + * notifications. + */ +#define GROWL_REG_DICT_EXTENSION XSTR("growlRegDict") + + +#define GROWL_POSITION_PREFERENCE_KEY @"GrowlSelectedPosition" + +#endif //ndef _GROWLDEFINES_H diff --git a/GrowlDefinesInternal.h b/GrowlDefinesInternal.h new file mode 100644 index 0000000..14067e3 --- /dev/null +++ b/GrowlDefinesInternal.h @@ -0,0 +1,430 @@ +// +// GrowlDefinesInternal.h +// Growl +// +// Created by Karl Adam on Mon May 17 2004. +// Copyright (c) 2004 the Growl Project. All rights reserved. +// + +#ifndef _GROWL_GROWLDEFINESINTERNAL_H +#define _GROWL_GROWLDEFINESINTERNAL_H + +#include +#include +#include + +#ifdef __OBJC__ +#define XSTR(x) (@x) +#else /* !__OBJC__ */ +#define XSTR CFSTR +#endif /* __OBJC__ */ + +/*! @header GrowlDefinesInternal.h + * @abstract Defines internal Growl macros and types. + * @ignore ATTRIBUTE_PACKED + * @discussion These constants are used both by GrowlHelperApp and by plug-ins. + * + * Notification keys (used in GrowlHelperApp, in GrowlApplicationBridge, and + * by applications that don't use GrowlApplicationBridge) are defined in + * GrowlDefines.h. + */ + +/*! @defined GROWL_TCP_PORT + * @abstract The TCP listen port for Growl notification servers. + */ +#define GROWL_TCP_PORT 23052 + +/*! @defined GROWL_UDP_PORT + * @abstract The UDP listen port for Growl notification servers. + */ +#define GROWL_UDP_PORT 9887 + +/*! @defined GROWL_PROTOCOL_VERSION + * @abstract The current version of the Growl network-notifications protocol (without encryption). + */ +#define GROWL_PROTOCOL_VERSION 1 + +/*! @defined GROWL_PROTOCOL_VERSION_AES128 +* @abstract The current version of the Growl network-notifications protocol (with AES-128 encryption). +*/ +#define GROWL_PROTOCOL_VERSION_AES128 2 + +/*! @defined GROWL_TYPE_REGISTRATION + * @abstract The packet type of registration packets with MD5 authentication. + */ +#define GROWL_TYPE_REGISTRATION 0 +/*! @defined GROWL_TYPE_NOTIFICATION + * @abstract The packet type of notification packets with MD5 authentication. + */ +#define GROWL_TYPE_NOTIFICATION 1 +/*! @defined GROWL_TYPE_REGISTRATION_SHA256 + * @abstract The packet type of registration packets with SHA-256 authentication. + */ +#define GROWL_TYPE_REGISTRATION_SHA256 2 +/*! @defined GROWL_TYPE_NOTIFICATION_SHA256 + * @abstract The packet type of notification packets with SHA-256 authentication. + */ +#define GROWL_TYPE_NOTIFICATION_SHA256 3 +/*! @defined GROWL_TYPE_REGISTRATION_NOAUTH +* @abstract The packet type of registration packets without authentication. +*/ +#define GROWL_TYPE_REGISTRATION_NOAUTH 4 +/*! @defined GROWL_TYPE_NOTIFICATION_NOAUTH +* @abstract The packet type of notification packets without authentication. +*/ +#define GROWL_TYPE_NOTIFICATION_NOAUTH 5 + +#define ATTRIBUTE_PACKED __attribute((packed)) + +/*! @struct GrowlNetworkPacket + * @abstract This struct is a header common to all incoming Growl network + * packets which identifies the type and version of the packet. + */ +struct GrowlNetworkPacket { + unsigned char version; + unsigned char type; +} ATTRIBUTE_PACKED; + +/*! + * @struct GrowlNetworkRegistration + * @abstract The format of a registration packet. + * @discussion A Growl client that wants to register with a Growl server sends + * a packet in this format. + * @field common The Growl packet header. + * @field appNameLen The name of the application that is registering. + * @field numAllNotifications The number of notifications in the list. + * @field numDefaultNotifications The number of notifications in the list that are enabled by default. + * @field data Variable-sized data. + */ +struct GrowlNetworkRegistration { + struct GrowlNetworkPacket common; + /* This name is used both internally and in the Growl + * preferences. + * + * The application name should remain stable between different versions + * and incarnations of your application. + * For example, "SurfWriter" is a good app name, whereas "SurfWriter 2.0" + * and "SurfWriter Lite" are not. + * + * In addition to being unsigned, the application name length is in + * network byte order. + */ + unsigned short appNameLen; + /* These names are used both internally and in the Growl + * preferences. For this reason, they should be human-readable. + */ + unsigned char numAllNotifications; + + unsigned char numDefaultNotifications; + /* The variable-sized data of a registration is: + * - The application name, in UTF-8 encoding, for appNameLen bytes. + * - The list of all notification names. + * - The list of default notifications, as 8-bit unsigned indices into the list of all notifications. + * - The MD5/SHA256 checksum of all the data preceding the checksum. + * + * Each notification name is encoded as: + * - Length: two bytes, unsigned, network byte order. + * - Name: As many bytes of UTF-8-encoded text as the length says. + * And there are numAllNotifications of these. + */ + unsigned char data[]; +} ATTRIBUTE_PACKED; + +/*! + * @struct GrowlNetworkNotification + * @abstract The format of a notification packet. + * @discussion A Growl client that wants to post a notification to a Growl + * server sends a packet in this format. + * @field common The Growl packet header. + * @field flags The priority number and the sticky bit. + * @field nameLen The length of the notification name. + * @field titleLen The length of the notification title. + * @field descriptionLen The length of the notification description. + * @field appNameLen The length of the application name. + * @field data Variable-sized data. + */ +struct GrowlNetworkNotification { + struct GrowlNetworkPacket common; + /*! + * @struct GrowlNetworkNotificationFlags + * @abstract Various flags. + * @discussion This 16-bit packed structure contains the priority as a + * signed 3-bit integer from -2 to +2, and the sticky flag as a single bit. + * The high 12 bits of the structure are reserved for future use. + * @field reserved reserved for future use. + * @field priority the priority as a signed 3-bit integer from -2 to +2. + * @field sticky the sticky flag. + */ + struct GrowlNetworkNotificationFlags { +#ifdef __BIG_ENDIAN__ + unsigned reserved: 12; + signed priority: 3; + unsigned sticky: 1; +#else + unsigned sticky: 1; + signed priority: 3; + unsigned reserved: 12; +#endif + } ATTRIBUTE_PACKED flags; //size = 16 (12 + 3 + 1) + + /* In addition to being unsigned, the notification name length + * is in network byte order. + */ + unsigned short nameLen; + /* @discussion In addition to being unsigned, the title length is in + * network byte order. + */ + unsigned short titleLen; + /* In addition to being unsigned, the description length is in + * network byte order. + */ + unsigned short descriptionLen; + /* In addition to being unsigned, the application name length + * is in network byte order. + */ + unsigned short appNameLen; + /* The variable-sized data of a notification is: + * - Notification name, in UTF-8 encoding, for nameLen bytes. + * - Title, in UTF-8 encoding, for titleLen bytes. + * - Description, in UTF-8 encoding, for descriptionLen bytes. + * - Application name, in UTF-8 encoding, for appNameLen bytes. + * - The MD5/SHA256 checksum of all the data preceding the checksum. + */ + unsigned char data[]; +} ATTRIBUTE_PACKED; + +/*! @defined GrowlEnabledKey + * @abstract Preference key controlling whether Growl is enabled. + * @discussion If this is false, then when GrowlHelperApp is launched to open + * a Growl registration dictionary file, GrowlHelperApp will quit when it has + * finished processing the file instead of listening for notifications. + */ +#define GrowlEnabledKey XSTR("GrowlEnabled") + +/*! @defined GROWL_SCREENSHOT_MODE + * @abstract Preference and notification key controlling whether to save a screenshot of the notification. + * @discussion This is for GHA's private usage. If your application puts this + * key into a notification dictionary, GHA will clobber it. This key is only + * allowed in the notification dictionaries GHA passes to displays. + * + * If this key contains an object whose boolValue is not NO, the display is + * asked to save a screenshot of the notification to + * ~/Library/Application\ Support/Growl/Screenshots. + */ +#define GROWL_SCREENSHOT_MODE XSTR("ScreenshotMode") + +/*! @defined GROWL_APP_LOCATION + * @abstract The location of this application. + * @discussion Contains either the POSIX path to the application, or a file-data dictionary (as used by the Dock). + * contains the file's alias record and its pathname. + */ +#define GROWL_APP_LOCATION XSTR("AppLocation") + +/*! @defined GROWL_REMOTE_ADDRESS + * @abstract The address of the host who sent this notification/registration. + * @discussion Contains an NSData with the address of the remote host who + * sent this notification/registration. + */ +#define GROWL_REMOTE_ADDRESS XSTR("RemoteAddress") + +/*! + * @defined GROWL_PREFPANE_BUNDLE_IDENTIFIER + * @discussion The bundle identifier for the Growl preference pane. + */ +#define GROWL_PREFPANE_BUNDLE_IDENTIFIER XSTR("com.growl.prefpanel") +/*! + * @defined GROWL_HELPERAPP_BUNDLE_IDENTIFIER + * @discussion The bundle identifier for the Growl background application (GrowlHelperApp). + */ +#define GROWL_HELPERAPP_BUNDLE_IDENTIFIER XSTR("com.Growl.GrowlHelperApp") + +/*! + * @defined GROWL_PREFPANE_NAME + * @discussion The file name of the Growl preference pane. + */ +#define GROWL_PREFPANE_NAME XSTR("Growl.prefPane") +#define PREFERENCE_PANES_SUBFOLDER_OF_LIBRARY XSTR("PreferencePanes") +#define PREFERENCE_PANE_EXTENSION XSTR("prefPane") + +//plug-in bundle filename extensions +#define GROWL_PLUGIN_EXTENSION XSTR("growlPlugin") +#define GROWL_PATHWAY_EXTENSION XSTR("growlPathway") +#define GROWL_VIEW_EXTENSION XSTR("growlView") +#define GROWL_STYLE_EXTENSION XSTR("growlStyle") + +/* --- These following macros are intended for plug-ins --- */ + +/*! @function SYNCHRONIZE_GROWL_PREFS + * @abstract Synchronizes Growl prefs so they're up-to-date. + * @discussion This macro is intended for use by GrowlHelperApp and by + * plug-ins (when the prefpane is selected). + */ +#define SYNCHRONIZE_GROWL_PREFS() CFPreferencesAppSynchronize(CFSTR("com.Growl.GrowlHelperApp")) + +/*! @function UPDATE_GROWL_PREFS + * @abstract Tells GrowlHelperApp to update its prefs. + * @discussion This macro is intended for use by plug-ins. + * It sends a notification to tell GrowlHelperApp to update its preferences. + */ +#define UPDATE_GROWL_PREFS() do { \ + SYNCHRONIZE_GROWL_PREFS(); \ + CFStringRef _key = CFSTR("pid"); \ + int pid = getpid(); \ + CFNumberRef _value = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &pid); \ + CFDictionaryRef userInfo = CFDictionaryCreate(kCFAllocatorDefault, (const void **)&_key, (const void **)&_value, 1, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); \ + CFRelease(_value); \ + CFNotificationCenterPostNotification(CFNotificationCenterGetDistributedCenter(), \ + CFSTR("GrowlPreferencesChanged"), \ + CFSTR("GrowlUserDefaults"), \ + userInfo, false); \ + CFRelease(userInfo); \ + } while(0) + +/*! @function READ_GROWL_PREF_VALUE + * @abstract Reads the given pref value from the plug-in's preferences. + * @discussion This macro is intended for use by plug-ins. It reads the value for the + * given key from the plug-in's preferences (which are stored in a dictionary inside of + * GrowlHelperApp's prefs). + * @param key The preference key to read the value of. + * @param domain The bundle ID of the plug-in. + * @param type The type of the result expected. + * @param result A pointer to an id. Set to the value if exists, left unchanged if not. + * + * If the value is set, you are responsible for releasing it. + */ +#define READ_GROWL_PREF_VALUE(key, domain, type, result) do {\ + CFDictionaryRef prefs = (CFDictionaryRef)CFPreferencesCopyAppValue((CFStringRef)domain, \ + CFSTR("com.Growl.GrowlHelperApp")); \ + if (prefs) {\ + if (CFDictionaryContainsKey(prefs, key)) {\ + *result = (type)CFDictionaryGetValue(prefs, key); \ + CFRetain(*result); \ + } \ + CFRelease(prefs); } \ + } while(0) + +/*! @function WRITE_GROWL_PREF_VALUE + * @abstract Writes the given pref value to the plug-in's preferences. + * @discussion This macro is intended for use by plug-ins. It writes the given + * value to the plug-in's preferences. + * @param key The preference key to write the value of. + * @param value The value to write to the preferences. It should be either a + * CoreFoundation type or toll-free bridged with one. + * @param domain The bundle ID of the plug-in. + */ +#define WRITE_GROWL_PREF_VALUE(key, value, domain) do {\ + CFDictionaryRef staticPrefs = (CFDictionaryRef)CFPreferencesCopyAppValue((CFStringRef)domain, \ + CFSTR("com.Growl.GrowlHelperApp")); \ + CFMutableDictionaryRef prefs; \ + if (staticPrefs == NULL) {\ + prefs = CFDictionaryCreateMutable(NULL, 0, NULL, NULL); \ + } else {\ + prefs = CFDictionaryCreateMutableCopy(NULL, 0, staticPrefs); \ + CFRelease(staticPrefs); \ + }\ + CFDictionarySetValue(prefs, key, value); \ + CFPreferencesSetAppValue((CFStringRef)domain, prefs, CFSTR("com.Growl.GrowlHelperApp")); \ + CFRelease(prefs); } while(0) + +/*! @function READ_GROWL_PREF_BOOL + * @abstract Reads the given Boolean from the plug-in's preferences. + * @discussion This is a wrapper around READ_GROWL_PREF_VALUE() intended for + * use with Booleans. + * @param key The preference key to read the Boolean from. + * @param domain The bundle ID of the plug-in. + * @param result A pointer to a Boolean type. Left unchanged if the value doesn't exist. + */ +#define READ_GROWL_PREF_BOOL(key, domain, result) do {\ + CFBooleanRef boolValue = NULL; \ + READ_GROWL_PREF_VALUE(key, domain, CFBooleanRef, &boolValue); \ + if (boolValue) {\ + *result = CFBooleanGetValue(boolValue); \ + CFRelease(boolValue); \ + } } while(0) + +/*! @function WRITE_GROWL_PREF_BOOL + * @abstract Writes the given Boolean to the plug-in's preferences. + * @discussion This is a wrapper around WRITE_GROWL_PREF_VALUE() intended for + * use with Booleans. + * @param key The preference key to write the Boolean for. + * @param value The Boolean value to write to the preferences. + * @param domain The bundle ID of the plug-in. + */ +#define WRITE_GROWL_PREF_BOOL(key, value, domain) do {\ + WRITE_GROWL_PREF_VALUE(key, value ? kCFBooleanTrue : kCFBooleanFalse, domain); } while(0) + +/*! @function READ_GROWL_PREF_INT + * @abstract Reads the given integer from the plug-in's preferences. + * @discussion This is a wrapper around READ_GROWL_PREF_VALUE() intended for + * use with integers. + * @param key The preference key to read the integer from. + * @param domain The bundle ID of the plug-in. + * @param result A pointer to an integer. Leaves unchanged if the value doesn't exist. + */ +#define READ_GROWL_PREF_INT(key, domain, result) do {\ + CFNumberRef intValue = NULL; \ + READ_GROWL_PREF_VALUE(key, domain, CFNumberRef, &intValue); \ + if (intValue) {\ + CFNumberGetValue(intValue, kCFNumberIntType, result); \ + CFRelease(intValue); \ + } } while(0) + +/*! @function WRITE_GROWL_PREF_INT + * @abstract Writes the given integer to the plug-in's preferences. + * @discussion This is a wrapper around WRITE_GROWL_PREF_VALUE() intended for + * use with integers. + * @param key The preference key to write the integer for. + * @param value The integer value to write to the preferences. + * @param domain The bundle ID of the plug-in. + */ +#define WRITE_GROWL_PREF_INT(key, value, domain) do {\ + CFNumberRef intValue = CFNumberCreate(NULL, kCFNumberIntType, &value); \ + WRITE_GROWL_PREF_VALUE(key, intValue, domain); \ + CFRelease(intValue); } while(0) + +/*! @function READ_GROWL_PREF_FLOAT + * @abstract Reads the given float from the plug-in's preferences. + * @discussion This is a wrapper around READ_GROWL_PREF_VALUE() intended for + * use with floats. + * @param key The preference key to read the float from. + * @param domain The bundle ID of the plug-in. + * @param result A pointer to a float. Leaves unchanged if the value doesn't exist. + */ +#define READ_GROWL_PREF_FLOAT(key, domain, result) do {\ + CFNumberRef floatValue = NULL; \ + READ_GROWL_PREF_VALUE(key, domain, CFNumberRef, &floatValue); \ + if (floatValue) {\ + CFNumberGetValue(floatValue, kCFNumberFloatType, result); \ + CFRelease(floatValue); \ + } } while(0) + +/*! @function WRITE_GROWL_PREF_FLOAT + * @abstract Writes the given float to the plug-in's preferences. + * @discussion This is a wrapper around WRITE_GROWL_PREF_VALUE() intended for + * use with floats. + * @param key The preference key to write the float for. + * @param value The float value to write to the preferences. + * @param domain The bundle ID of the plug-in. + */ +#define WRITE_GROWL_PREF_FLOAT(key, value, domain) do {\ + CFNumberRef floatValue = CFNumberCreate(NULL, kCFNumberFloatType, &value); \ + WRITE_GROWL_PREF_VALUE(key, floatValue, domain); \ + CFRelease(floatValue); } while(0) + + +/*! @defined GROWL_CLOSE_ALL_NOTIFICATIONS + * @abstract Notification to close all Growl notifications + * @discussion Should be posted to the default notification center when a close widget is option+clicked. + * All notifications should close in response. + */ +#define GROWL_CLOSE_ALL_NOTIFICATIONS XSTR("GrowlCloseAllNotifications") + +#pragma mark Small utilities + +/*! + * @defined FLOAT_EQ(x,y) + * @abstract Compares two floats. + */ +#define FLOAT_EQ(x,y) (((y - FLT_EPSILON) < x) && (x < (y + FLT_EPSILON))) + +#endif //ndef _GROWL_GROWLDEFINESINTERNAL_H diff --git a/GrowlDisplayPlugin.h b/GrowlDisplayPlugin.h new file mode 100644 index 0000000..1cb30c3 --- /dev/null +++ b/GrowlDisplayPlugin.h @@ -0,0 +1,75 @@ +// +// GrowlDisplayPlugin.h +// Growl +// +// Created by Mac-arena the Bored Zo on 2005-06-01. +// Copyright 2005-2006 The Growl Project. All rights reserved. +// + +#import +#import "GrowlPlugin.h" + +@class GrowlApplicationNotification, GrowlNotificationDisplayBridge; +@class GrowlDisplayWindowController; + +//Info.plist keys for plug-in bundles. +extern NSString *GrowlDisplayPluginInfoKeyUsesQueue; +extern NSString *GrowlDisplayPluginInfoKeyWindowNibName; + +/*! + * @class GrowlDisplayPlugin + * @abstract Base class for all display plugins. + */ +@interface GrowlDisplayPlugin : GrowlPlugin { + Class windowControllerClass; + + //for all displays + NSMutableDictionary *coalescableBridges; + + //for non-queueing displays + NSMutableArray *activeBridges; //GrowlNotificationDisplayBridges currently being displayed + + //for queueing displays + GrowlNotificationDisplayBridge *bridge; + NSMutableArray *queue; //GrowlNotificationDisplayBridges yet to be displayed +} + +/*! @method displayNotification: + * @abstract Display a notification to the user. + * @param notification The notification to display. + * @discussion Unless you have a specific reason to override this method you should not do so. + * All the magic should happen in configureBridge: + */ +- (void) displayNotification:(GrowlApplicationNotification *)notification; + +/*! @method configureBridge: + * @abstract Configures the chosen bridge before a notificaion is displayed. + * @param bridge The bridge to configure. + * @discussion This is the place where the magic happens. Override this method and do any + * specific configuration here. This is the last port-of-call before a notification is displayed. + * The default implementation does nothing so it is important that you override and provide an + * implementation. + */ +- (void) configureBridge:(GrowlNotificationDisplayBridge *)theBridge; + +/*! @method windowNibName + * @abstract Returns the name of the display's sole nib file (resulting in + * the creation of a window controller for the window in that file). + * @discussion When subclassing GrowlDisplayPlugin, override this + * method and return the name of the nib (without the ".nib" extension) that + * contains the display window. This method is called by + * displayNotification: to create a + * GrowlNotificationDisplayBridge, which is the File's Owner for + * the nib. + * + * The default implementation returns the value of + * GrowlDisplayWindowNibName in the Info.plist of the bundle for + * the display plug-in. + * @result The name of the window nib. + */ +- (NSString *) windowNibName; + +/* */ +- (void) displayWindowControllerDidTakeDownWindow:(GrowlDisplayWindowController *)wc; + +@end diff --git a/GrowlITTSW.xcodeproj/TemplateIcon.icns b/GrowlITTSW.xcodeproj/TemplateIcon.icns new file mode 100644 index 0000000000000000000000000000000000000000..62cb7015e09d6ea3e65d7f7949c4c07f9246a908 GIT binary patch literal 52318 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 WhsmLR^b3` +#import +#import "GrowlITTSWWindow.h" + +@interface GrowlITTSWController : ITSharedController { + GrowlITTSWWindow *_window; +} + +- (void)showWindowWithText:(NSString *)text image:(NSImage *)image; + +@end diff --git a/GrowlITTSWController.m b/GrowlITTSWController.m new file mode 100644 index 0000000..0f1cd0e --- /dev/null +++ b/GrowlITTSWController.m @@ -0,0 +1,93 @@ +// +// GrowlITTSWController.m +// Growl +// +// Created by Joseph Spiros on 2/28/09. +// Copyright 2009 iThink Software. All rights reserved. +// + +#import "GrowlITTSWController.h" +#import "GrowlITTSWWindow.h" + +#import +#import + +@implementation NSImage (SmoothAdditions) + +- (NSImage *)imageScaledSmoothlyToSize:(NSSize)scaledSize +{ + NSImage *newImage; + NSImageRep *rep = [self bestRepresentationForDevice:nil]; + + newImage = [[NSImage alloc] initWithSize:scaledSize]; + [newImage lockFocus]; + { + [[NSGraphicsContext currentContext] setImageInterpolation:NSImageInterpolationHigh]; + [[NSGraphicsContext currentContext] setShouldAntialias:YES]; + [rep drawInRect:NSMakeRect(3, 3, scaledSize.width - 6, scaledSize.height - 6)]; + } + [newImage unlockFocus]; + return [newImage autorelease]; +} + +@end + +@implementation GrowlITTSWController + +- (id)init +{ + if ( ( self = [super init] ) ) { + NSArray *screens = [NSScreen screens]; + + _window = [[GrowlITTSWWindow sharedWindow] retain]; + + [_window setScreen:[screens objectAtIndex:0]]; + + [_window setExitMode:ITTransientStatusWindowExitAfterDelay]; + [_window setExitDelay:4.0]; + + [_window setHorizontalPosition:ITWindowPositionRight]; + [_window setVerticalPosition:ITWindowPositionTop]; + + [_window setSizing:ITTransientStatusWindowMini]; + + [_window setEntryEffect:[[[NSClassFromString(@"ITSlideVerticallyWindowEffect") alloc] initWithWindow:_window] autorelease]]; + [_window setExitEffect:[[[NSClassFromString(@"ITSlideHorizontallyWindowEffect") alloc] initWithWindow:_window] autorelease]]; + + [[_window entryEffect] setEffectTime:0.8]; + [[_window exitEffect] setEffectTime:0.8]; + + [(ITTSWBackgroundView *)[_window contentView]setBackgroundMode: + ITTSWBackgroundReadable]; + } + + return self; +} + +- (void)dealloc +{ + [_window release]; + [super dealloc]; +} + +- (void)showWindowWithText:(NSString *)text image:(NSImage *)image +{ + NSMutableAttributedString *attributedText = [[NSMutableAttributedString alloc] initWithString:text]; + NSSize newSize; + NSSize oldSize = [image size]; + + if (oldSize.width > oldSize.height) { + newSize = NSMakeSize(110.0f, (oldSize.height * (110.0f / oldSize.width))); + } else { + newSize = NSMakeSize((oldSize.width * (110.0f / oldSize.height)), 110.0f); + } + + image = [[[[NSImage alloc] initWithData:[image TIFFRepresentation]] autorelease] imageScaledSmoothlyToSize:newSize]; + + [_window setImage:image]; + [_window buildTextWindowWithString:attributedText]; + [_window appear:self]; + [attributedText release]; +} + +@end \ No newline at end of file diff --git a/GrowlITTSWDisplay.h b/GrowlITTSWDisplay.h new file mode 100644 index 0000000..1330358 --- /dev/null +++ b/GrowlITTSWDisplay.h @@ -0,0 +1,11 @@ +#import +#import "GrowlDisplayPlugin.h" + +@class GrowlApplicationNotification; + +@interface GrowlITTSWDisplay : GrowlDisplayPlugin { +} + +- (void) displayNotification:(GrowlApplicationNotification *)notification; + +@end diff --git a/GrowlITTSWDisplay.m b/GrowlITTSWDisplay.m new file mode 100644 index 0000000..bbd7515 --- /dev/null +++ b/GrowlITTSWDisplay.m @@ -0,0 +1,42 @@ +#import "GrowlITTSWDisplay.h" +#import "GrowlITTSWController.h" +#import "GrowlITTSWPrefs.h" +#import "GrowlDefines.h" +#import "GrowlDefinesInternal.h" +#import "GrowlApplicationNotification.h" + +@implementation GrowlITTSWDisplay + +- (void) dealloc { + [preferencePane release]; + [super dealloc]; +} + +/* - (NSPreferencePane *) preferencePane { + if (!preferencePane) + preferencePane = [[GrowlITTSWPrefs alloc] initWithBundle:[NSBundle bundleWithIdentifier:@"com.ithinksw.growl.ittsw"]]; + return preferencePane; +}*/ + +//we implement requiresPositioning entirely because it was added as a requirement for doing 1.1 plugins, however +//we don't really care if positioning is required or not, because we are only ever in the menubar. +- (BOOL)requiresPositioning { + return NO; +} + +#pragma mark - +- (void) displayNotification:(GrowlApplicationNotification *)notification { + NSDictionary *dict = [notification dictionaryRepresentation]; + NSString *title = [dict objectForKey:GROWL_NOTIFICATION_TITLE]; + NSString *desc = [dict objectForKey:GROWL_NOTIFICATION_DESCRIPTION]; + NSImage *image = [dict objectForKey:GROWL_NOTIFICATION_ICON]; + NSString *text; + if (desc) { + text = [title stringByAppendingFormat:@"\n%@", desc]; + } else { + text = title; + } + [[GrowlITTSWController sharedController] showWindowWithText:text image:image]; +} + +@end diff --git a/GrowlITTSWPrefs.h b/GrowlITTSWPrefs.h new file mode 100644 index 0000000..e2c287d --- /dev/null +++ b/GrowlITTSWPrefs.h @@ -0,0 +1,6 @@ +#import + +@interface GrowlITTSWPrefs : NSPreferencePane { +} + +@end diff --git a/GrowlITTSWPrefs.m b/GrowlITTSWPrefs.m new file mode 100644 index 0000000..395b2ff --- /dev/null +++ b/GrowlITTSWPrefs.m @@ -0,0 +1,6 @@ +#import "GrowlITTSWPrefs.h" +#import "GrowlDefinesInternal.h" + +@implementation GrowlITTSWPrefs + +@end diff --git a/GrowlITTSWWindow.h b/GrowlITTSWWindow.h new file mode 100644 index 0000000..2dc64c9 --- /dev/null +++ b/GrowlITTSWWindow.h @@ -0,0 +1,15 @@ +#import +#import + +#define SMALL_DIVISOR 1.33333 +#define MINI_DIVISOR 1.66667 + +@interface GrowlITTSWWindow : ITTransientStatusWindow { + NSImage *_image; + NSTextField *_textField; +} + +- (void)setImage:(NSImage *)newImage; +- (void)buildTextWindowWithString:(id)text; + +@end diff --git a/GrowlITTSWWindow.m b/GrowlITTSWWindow.m new file mode 100644 index 0000000..0616224 --- /dev/null +++ b/GrowlITTSWWindow.m @@ -0,0 +1,252 @@ +// +// GrowlITTSWWindow.m +// Growl +// +// Created by Joseph Spiros on 2/28/09. +// Copyright 2009 iThink Software. All rights reserved. +// + +#import "GrowlITTSWWindow.h" + +#define SW_PAD 24.00 +#define SW_SPACE 24.00 +#define SW_MINW 211.00 +#define SW_BORDER 32.00 +#define SW_METER_PAD 4.00 +#define SW_BUTTON_PAD_R 30.00 +#define SW_BUTTON_PAD_B 24.00 +#define SW_BUTTON_DIV 12.00 +#define SW_BUTTON_EXTRA_W 8.00 +#define SW_SHADOW_SAT 1.25 + +@interface GrowlITTSWWindow (Private) +- (NSRect)setupWindowWithDataSize:(NSSize)dataSize; +@end + +@implementation GrowlITTSWWindow + + +/*************************************************************************/ +#pragma mark - +#pragma mark INITIALIZATION / DEALLOCATION METHODS +/*************************************************************************/ + +- (id)initWithContentView:(NSView *)contentView + exitMode:(ITTransientStatusWindowExitMode)exitMode + backgroundType:(ITTransientStatusWindowBackgroundType)backgroundType +{ + if ( ( self = [super initWithContentView:contentView + exitMode:exitMode + backgroundType:backgroundType] ) ) { + // Set default values. + _image = [[NSImage imageNamed:@"NSApplicationIcon"] retain]; + _sizing = ITTransientStatusWindowRegular; + } + + return self; +} + +- (void)dealloc +{ + [_image release]; + [super dealloc]; +} + + +/*************************************************************************/ +#pragma mark - +#pragma mark ACCESSOR METHODS +/*************************************************************************/ + +- (void)setImage:(NSImage *)newImage +{ + [_image autorelease]; + _image = [newImage copy]; +} + +- (void)setSizing:(ITTransientStatusWindowSizing)newSizing +{ + _sizing = newSizing; +} + +/*************************************************************************/ +#pragma mark - +#pragma mark INSTANCE METHODS +/*************************************************************************/ + +- (void)appear:(id)sender +{ + [super appear:sender]; +} + +- (void)vanish:(id)sender +{ + [super vanish:sender]; +} + +- (NSRect)setupWindowWithDataSize:(NSSize)dataSize +{ + float divisor = 1.0; + NSRect imageRect; + float imageWidth = 0.0; + float imageHeight = 0.0; + float dataWidth = dataSize.width; + float dataHeight = dataSize.height; + float contentHeight = 0.0; + float windowWidth = 0.0; + float windowHeight = 0.0; + NSRect visibleFrame = [[self screen] visibleFrame]; + NSPoint screenOrigin = visibleFrame.origin; + float screenWidth = visibleFrame.size.width; + float screenHeight = visibleFrame.size.height; + float maxWidth = ( screenWidth - (SW_BORDER * 2) ); + float maxHeight = ( screenHeight - (SW_BORDER * 2) ); + float excessWidth = 0.0; + float excessHeight = 0.0; + NSPoint windowOrigin = NSZeroPoint; + ITImageView *imageView; + BOOL shouldAnimate = ( ! (([self visibilityState] == ITWindowAppearingState) || + ([self visibilityState] == ITWindowVanishingState)) ); + + if ( _sizing == ITTransientStatusWindowSmall ) { + divisor = SMALL_DIVISOR; + } else if ( _sizing == ITTransientStatusWindowMini ) { + divisor = MINI_DIVISOR; + } + + // Get image width and height. + imageWidth = ( [_image size].width / divisor ); + imageHeight = ( [_image size].height / divisor ); + + // Set the content height to the greater of the text and image heights. + contentHeight = ( ( imageHeight > dataHeight ) ? imageHeight : dataHeight ); + + // Setup the Window, and remove all its contentview's subviews. + windowWidth = ( (SW_PAD / divisor) + imageWidth + ((dataWidth > 0) ? (SW_SPACE / divisor) + dataWidth : 0) + (SW_PAD / divisor) ); + windowHeight = ( (SW_PAD / divisor) + contentHeight + (SW_PAD / divisor) ); + + // Constrain size to max limits. Adjust data sizes accordingly. + excessWidth = (windowWidth - maxWidth ); + excessHeight = (windowHeight - maxHeight); + + if ( excessWidth > 0.0 ) { + windowWidth = maxWidth; + dataWidth -= excessWidth; + } + + if ( excessHeight > 0.0 ) { + windowHeight = maxHeight; + dataHeight -= excessHeight; + } + + if ( [self horizontalPosition] == ITWindowPositionLeft ) { + windowOrigin.x = ( SW_BORDER + screenOrigin.x ); + } else if ( [self horizontalPosition] == ITWindowPositionCenter ) { + windowOrigin.x = ( screenOrigin.x + (screenWidth / 2) - (windowWidth / 2) ); + } else if ( [self horizontalPosition] == ITWindowPositionRight ) { + windowOrigin.x = ( screenOrigin.x + screenWidth - (windowWidth + SW_BORDER) ); + } + + if ( [self verticalPosition] == ITWindowPositionTop ) { + windowOrigin.y = ( screenOrigin.y + screenHeight - (windowHeight + SW_BORDER) ); + } else if ( [self verticalPosition] == ITWindowPositionMiddle ) { + // Middle-oriented windows should be slightly proud of the screen's middle. + windowOrigin.y = ( (screenOrigin.y + (screenHeight / 2) - (windowHeight / 2)) + (screenHeight / 8) ); + } else if ( [self verticalPosition] == ITWindowPositionBottom ) { + windowOrigin.y = ( SW_BORDER + screenOrigin.y ); + } + + [self setFrame:NSMakeRect( windowOrigin.x, + windowOrigin.y, + windowWidth, + windowHeight) display:YES animate:shouldAnimate]; + + [[[self contentView] subviews] makeObjectsPerformSelector:@selector(removeFromSuperview)]; + + // Setup, position, fill, and add the image view to the content view. + imageRect = NSMakeRect( (SW_PAD / divisor) + ((dataWidth > 0) ? 4 : 0), + ((SW_PAD / divisor) + ((contentHeight - imageHeight) / 2)), + imageWidth, + imageHeight ); + imageView = [[[ITImageView alloc] initWithFrame:imageRect] autorelease]; + [imageView setAutoresizingMask:(NSViewMinYMargin | NSViewMaxYMargin)]; + [imageView setImage:_image]; + [imageView setCastsShadow:YES]; + [[self contentView] addSubview:imageView]; + + return NSMakeRect( ((SW_PAD / divisor) + imageWidth + (SW_SPACE / divisor)), + ((SW_PAD / divisor) + ((contentHeight - dataHeight) / 2)), + dataWidth, + dataHeight); +} + +- (void)buildTextWindowWithString:(id)text +{ + float divisor = 1.0; + float dataWidth = 0.0; + float dataHeight = 0.0; + NSRect dataRect; + NSArray *lines = [(([text isKindOfClass:[NSString class]]) ? text : [text mutableString]) componentsSeparatedByString:@"\n"]; + id oneLine = nil; + NSEnumerator *lineEnum = [lines objectEnumerator]; + float baseFontSize = 18.0; + ITTextField *textField; + NSFont *font; + NSDictionary *attr; + + if ( _sizing == ITTransientStatusWindowSmall ) { + divisor = SMALL_DIVISOR; + } else if ( _sizing == ITTransientStatusWindowMini ) { + divisor = MINI_DIVISOR; + } + + font = [NSFont fontWithName:@"LucidaGrande-Bold" size:(baseFontSize / divisor)]; + attr = [NSDictionary dictionaryWithObject:font forKey:NSFontAttributeName]; + + // Iterate over each line to get text width and height + while ( (oneLine = [lineEnum nextObject]) ) { + // Get the width of one line, adding 8.0 because Apple sucks donkey rectum. + float oneLineWidth = ( [oneLine sizeWithAttributes:attr].width + 8.0 ); + // Add the height of this line to the total text height + dataHeight += [oneLine sizeWithAttributes:attr].height; + // If this line wider than the last one, set it as the text width. + dataWidth = ( ( dataWidth > oneLineWidth ) ? dataWidth : oneLineWidth ); + } + + // Add 4.0 to the final dataHeight to accomodate the shadow. + dataHeight += 4.0; + + dataRect = [self setupWindowWithDataSize:NSMakeSize(dataWidth, dataHeight)]; + + // Create, position, setup, fill, and add the text view to the content view. + textField = [[[ITTextField alloc] initWithFrame:dataRect] autorelease]; + [textField setAutoresizingMask:(NSViewHeightSizable | NSViewWidthSizable)]; + [textField setEditable:NO]; + [textField setSelectable:NO]; + [textField setBordered:NO]; + [textField setDrawsBackground:NO]; + [textField setFont:font]; + [textField setTextColor:[NSColor whiteColor]]; + [textField setCastsShadow:YES]; + [[textField cell] setWraps:NO]; + + if ([text isKindOfClass:[NSString class]]) { + [textField setStringValue:text]; + } else { + [textField setAttributedStringValue:text]; + } + + [textField setShadowSaturation:SW_SHADOW_SAT]; + [[self contentView] addSubview:textField]; + + // Display the window. + [[self contentView] setNeedsDisplay:YES]; + _textField = textField; +} + +- (NSTimeInterval)animationResizeTime:(NSRect)newFrame +{ + return (NSTimeInterval)0.25; +} + +@end diff --git a/GrowlITTSW_Prefix.pch b/GrowlITTSW_Prefix.pch new file mode 100644 index 0000000..399baf0 --- /dev/null +++ b/GrowlITTSW_Prefix.pch @@ -0,0 +1,7 @@ +// +// Prefix header for all source files of the 'GrowlITTSW' target in the 'GrowlITTSW' project. +// + +#ifdef __OBJC__ + #import +#endif diff --git a/GrowlPlugin.h b/GrowlPlugin.h new file mode 100644 index 0000000..fd3d30a --- /dev/null +++ b/GrowlPlugin.h @@ -0,0 +1,105 @@ +// +// GrowlPlugin.h +// Growl +// +// Created by Mac-arena the Bored Zo on 2005-06-01. +// Copyright 2005-2006 The Growl Project. All rights reserved. +// + +#import + +@class NSPreferencePane; + +/*! @class GrowlPlugin + * @abstract The base plug-in class. + * @discussion All Growl plug-in instances are a kind of this class, including + * display plug-ins, which are kinds of GrowlDisplayPlugin. + */ +@interface GrowlPlugin : NSObject { + NSString *pluginName, *pluginAuthor, *pluginVersion, *pluginDesc; + NSBundle *pluginBundle; + NSString *pluginPathname; + + NSPreferencePane *preferencePane; + NSString *prefDomain; +} + +/*! + * @method initWithName:author:version:pathname: + * @abstract Designated initializer. + * @param name The name of the plugin. + * @param author The author of the plugin. + * @param version The version of the plugin. + * @param pathname The pathname of the plugin. + * @result An initialized GrowlPlugin object. + */ +- (id) initWithName:(NSString *)name author:(NSString *)author version:(NSString *)version pathname:(NSString *)pathname; + +/*! + * @method initWithBundle: + * @abstract Initializer for plug-ins in bundles. The name, author, version, and pathname will be obtained from the bundle. + * @result An initialized GrowlPlugin object. + */ +- (id) initWithBundle:(NSBundle *)bundle; + + +/*! + * @method name + * @abstract Returns the name of the receiver. + */ +- (NSString *) name; + +/*! + * @method author + * @abstract Returns the author of the receiver. + */ +- (NSString *) author; + +/*! + * @method pluginDescription + * @abstract Returns the description of the receiver. + */ +- (NSString *) pluginDescription; + +/*! + * @method version + * @abstract Returns the version of the receiver. + */ +- (NSString *) version; + +/*! + * @method bundle + * @abstract Returns the bundle of the receiver. + */ +- (NSBundle *) bundle; + +/*! + * @method pathname + * @abstract Returns the pathname of the receiver. + */ +- (NSString *) pathname; + +/*! +* @method pathname + * @abstract Returns the string used to access the preference domain of the receiver. + */ +- (NSString *) prefDomain; + + +/*! @method preferencePane + * @abstract Return an NSPreferencePane instance that manages + * the plugin's preferences. + * @discussion Your plug-in should put the controls for its preferences in + * this preference pane. + * + * Currently, the size of the preference pane's view should be 354 pixels by + * 289 pixels, but you should set the springs of the view and its subviews + * under the assumption that it can be resized horizontally and vertically to + * any size. + * + * The default implementation of this method returns nil. + * @result The preference pane. Can be nil. + */ +- (NSPreferencePane *) preferencePane; + +@end diff --git a/Info.plist b/Info.plist new file mode 100644 index 0000000..7bc2d5d --- /dev/null +++ b/Info.plist @@ -0,0 +1,32 @@ + + + + + CFBundleDevelopmentRegion + English + CFBundleExecutable + GrowlITTSW + CFBundleIdentifier + com.ithinksw.growl-ittsw + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + GrowlITTSW + CFBundlePackageType + DISP + CFBundleSignature + GRRR + CFBundleVersion + 1.0 + CSResourcesFileMapped + yes + GrowlDisplayUsesQueue + + GrowlPluginAuthor + iThink Software + GrowlPluginDescription + ITTSW uses the ITTransientStatusWindow class from ITKit + NSPrincipalClass + GrowlITTSWDisplay + + -- 2.20.1