mirror of
				https://github.com/notepad-plus-plus/notepad-plus-plus.git
				synced 2025-10-31 19:44:06 +01:00 
			
		
		
		
	
		
			
				
	
	
		
			449 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			Plaintext
		
	
	
	
	
	
			
		
		
	
	
			449 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			Plaintext
		
	
	
	
	
	
| 
 | |
| /**
 | |
|  * Scintilla source code edit control
 | |
|  * InfoBar.mm - Implements special info bar with zoom info, caret position etc. to be used with
 | |
|  *              ScintillaView.
 | |
|  *
 | |
|  * Mike Lischke <mlischke@sun.com>
 | |
|  *
 | |
|  * Copyright 2009 Sun Microsystems, Inc. All rights reserved.
 | |
|  * This file is dual licensed under LGPL v2.1 and the Scintilla license (http://www.scintilla.org/License.txt).
 | |
|  */
 | |
| 
 | |
| #import "InfoBar.h"
 | |
| 
 | |
| //--------------------------------------------------------------------------------------------------
 | |
| 
 | |
| @implementation VerticallyCenteredTextFieldCell
 | |
| 
 | |
| // Inspired by code from Daniel Jalkut, Red Sweater Software.
 | |
| 
 | |
| - (NSRect) drawingRectForBounds: (NSRect) theRect
 | |
| {
 | |
| 	// Get the parent's idea of where we should draw
 | |
| 	NSRect newRect = [super drawingRectForBounds: theRect];
 | |
| 
 | |
| 	// When the text field is being edited or selected, we have to turn off the magic because it
 | |
|   // screws up the configuration of the field editor. We sneak around this by intercepting
 | |
|   // selectWithFrame and editWithFrame and sneaking a reduced, centered rect in at the last minute.
 | |
| 	if (mIsEditingOrSelecting == NO)
 | |
| 	{
 | |
| 		// Get our ideal size for current text
 | |
| 		NSSize textSize = [self cellSizeForBounds: theRect];
 | |
| 
 | |
| 		// Center that in the proposed rect
 | |
| 		CGFloat heightDelta = newRect.size.height - textSize.height;
 | |
| 		if (heightDelta > 0)
 | |
| 		{
 | |
| 			newRect.size.height -= heightDelta;
 | |
| 			newRect.origin.y += ceil(heightDelta / 2);
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return newRect;
 | |
| }
 | |
| 
 | |
| //--------------------------------------------------------------------------------------------------
 | |
| 
 | |
| - (void) selectWithFrame: (NSRect) aRect inView: (NSView*) controlView editor: (NSText*) textObj
 | |
|                 delegate:(id) anObject start: (NSInteger) selStart length: (NSInteger) selLength
 | |
| {
 | |
| 	aRect = [self drawingRectForBounds: aRect];
 | |
| 	mIsEditingOrSelecting = YES;
 | |
| 	[super selectWithFrame: aRect
 | |
|                   inView: controlView
 | |
|                   editor: textObj
 | |
|                 delegate: anObject
 | |
|                    start: selStart
 | |
|                   length: selLength];
 | |
| 	mIsEditingOrSelecting = NO;
 | |
| }
 | |
| 
 | |
| //--------------------------------------------------------------------------------------------------
 | |
| 
 | |
| - (void) editWithFrame: (NSRect) aRect inView: (NSView*) controlView editor: (NSText*) textObj
 | |
|               delegate: (id) anObject event: (NSEvent*) theEvent
 | |
| {
 | |
| 	aRect = [self drawingRectForBounds: aRect];
 | |
| 	mIsEditingOrSelecting = YES;
 | |
| 	[super editWithFrame: aRect
 | |
|                 inView: controlView
 | |
|                 editor: textObj
 | |
|               delegate: anObject
 | |
|                  event: theEvent];
 | |
| 	mIsEditingOrSelecting = NO;
 | |
| }
 | |
| 
 | |
| @end
 | |
| 
 | |
| //--------------------------------------------------------------------------------------------------
 | |
| 
 | |
| @implementation InfoBar
 | |
| 
 | |
| - (id) initWithFrame: (NSRect) frame
 | |
| {
 | |
|   self = [super initWithFrame: frame];
 | |
|   if (self)
 | |
|   {
 | |
|     NSBundle* bundle = [NSBundle bundleForClass: [InfoBar class]];
 | |
| 
 | |
|     NSString* path = [bundle pathForResource: @"info_bar_bg" ofType: @"tiff" inDirectory: nil];
 | |
|     mBackground = [[NSImage alloc] initWithContentsOfFile: path];
 | |
|     if (![mBackground isValid])
 | |
|       NSLog(@"Background image for info bar is invalid.");
 | |
| 
 | |
|     mScaleFactor = 1.0;
 | |
|     mCurrentCaretX = 0;
 | |
|     mCurrentCaretY = 0;
 | |
|     [self createItems];
 | |
|   }
 | |
|   return self;
 | |
| }
 | |
| 
 | |
| //--------------------------------------------------------------------------------------------------
 | |
| 
 | |
| /**
 | |
|  * Called by a connected component (usually the info bar) if something changed there.
 | |
|  *
 | |
|  * @param type The type of the notification.
 | |
|  * @param message Carries the new status message if the type is a status message change.
 | |
|  * @param location Carries the new location (e.g. caret) if the type is a caret change or similar type.
 | |
|  * @param value Carries the new zoom value if the type is a zoom change.
 | |
|  */
 | |
| - (void) notify: (NotificationType) type message: (NSString*) message location: (NSPoint) location
 | |
|   value: (float) value
 | |
| {
 | |
|   switch (type)
 | |
|   {
 | |
|     case IBNZoomChanged:
 | |
|       [self setScaleFactor: value adjustPopup: YES];
 | |
|       break;
 | |
|     case IBNCaretChanged:
 | |
|       [self setCaretPosition: location];
 | |
|       break;
 | |
|     case IBNStatusChanged:
 | |
|       [mStatusTextLabel setStringValue: message];
 | |
|       break;
 | |
|   }
 | |
| }
 | |
| 
 | |
| //--------------------------------------------------------------------------------------------------
 | |
| 
 | |
| /**
 | |
|  * Used to set a protocol object we can use to send change notifications to.
 | |
|  */
 | |
| - (void) setCallback: (id <InfoBarCommunicator>) callback
 | |
| {
 | |
|   mCallback = callback;
 | |
| }
 | |
| 
 | |
| //--------------------------------------------------------------------------------------------------
 | |
| 
 | |
| static NSString *DefaultScaleMenuLabels[] = {
 | |
|   @"20%", @"30%", @"50%", @"75%", @"100%", @"130%", @"160%", @"200%", @"250%", @"300%"
 | |
| };
 | |
| static float DefaultScaleMenuFactors[] = {
 | |
|   0.2f, 0.3f, 0.5f, 0.75f, 1.0f, 1.3f, 1.6f, 2.0f, 2.5f, 3.0f
 | |
| };
 | |
| static unsigned DefaultScaleMenuSelectedItemIndex = 4;
 | |
| static float BarFontSize = 10.0;
 | |
| 
 | |
| - (void) createItems
 | |
| {
 | |
|   // 1) The zoom popup.
 | |
|   unsigned numberOfDefaultItems = sizeof(DefaultScaleMenuLabels) / sizeof(NSString *);
 | |
| 
 | |
|   // Create the popup button.
 | |
|   mZoomPopup = [[NSPopUpButton allocWithZone:[self zone]] initWithFrame: NSMakeRect(0.0, 0.0, 1.0, 1.0) pullsDown: NO];
 | |
| 
 | |
|   // No border or background please.
 | |
|   [[mZoomPopup cell] setBordered: NO];
 | |
|   [[mZoomPopup cell] setArrowPosition: NSPopUpArrowAtBottom];
 | |
| 
 | |
|   // Fill it.
 | |
|   for (unsigned count = 0; count < numberOfDefaultItems; count++)
 | |
|   {
 | |
|     [mZoomPopup addItemWithTitle: NSLocalizedStringFromTable(DefaultScaleMenuLabels[count], @"ZoomValues", nil)];
 | |
|     id currentItem = [mZoomPopup itemAtIndex: count];
 | |
|     if (DefaultScaleMenuFactors[count] != 0.0)
 | |
|       [currentItem setRepresentedObject: [NSNumber numberWithFloat: DefaultScaleMenuFactors[count]]];
 | |
|   }
 | |
|   [mZoomPopup selectItemAtIndex: DefaultScaleMenuSelectedItemIndex];
 | |
| 
 | |
|   // Hook it up.
 | |
|   [mZoomPopup setTarget: self];
 | |
|   [mZoomPopup setAction: @selector(zoomItemAction:)];
 | |
| 
 | |
|   // Set a suitable font.
 | |
|   [mZoomPopup setFont: [NSFont menuBarFontOfSize: BarFontSize]];
 | |
| 
 | |
|   // Make sure the popup is big enough to fit the cells.
 | |
|   [mZoomPopup sizeToFit];
 | |
| 
 | |
|   // Don't let it become first responder
 | |
|   [mZoomPopup setRefusesFirstResponder: YES];
 | |
| 
 | |
|   // put it in the scrollview.
 | |
|   [self addSubview: mZoomPopup];
 | |
|   [mZoomPopup release];
 | |
| 
 | |
|   // 2) The caret position label.
 | |
|   Class oldCellClass = [NSTextField cellClass];
 | |
|   [NSTextField setCellClass: [VerticallyCenteredTextFieldCell class]];
 | |
| 
 | |
|   mCaretPositionLabel = [[NSTextField alloc] initWithFrame: NSMakeRect(0.0, 0.0, 50.0, 1.0)];
 | |
|   [mCaretPositionLabel setBezeled: NO];
 | |
|   [mCaretPositionLabel setBordered: NO];
 | |
|   [mCaretPositionLabel setEditable: NO];
 | |
|   [mCaretPositionLabel setSelectable: NO];
 | |
|   [mCaretPositionLabel setDrawsBackground: NO];
 | |
|   [mCaretPositionLabel setFont: [NSFont menuBarFontOfSize: BarFontSize]];
 | |
| 
 | |
|   NSTextFieldCell* cell = [mCaretPositionLabel cell];
 | |
|   [cell setPlaceholderString: @"0:0"];
 | |
|   [cell setAlignment: NSCenterTextAlignment];
 | |
| 
 | |
|   [self addSubview: mCaretPositionLabel];
 | |
|   [mCaretPositionLabel release];
 | |
| 
 | |
|   // 3) The status text.
 | |
|   mStatusTextLabel = [[NSTextField alloc] initWithFrame: NSMakeRect(0.0, 0.0, 1.0, 1.0)];
 | |
|   [mStatusTextLabel setBezeled: NO];
 | |
|   [mStatusTextLabel setBordered: NO];
 | |
|   [mStatusTextLabel setEditable: NO];
 | |
|   [mStatusTextLabel setSelectable: NO];
 | |
|   [mStatusTextLabel setDrawsBackground: NO];
 | |
|   [mStatusTextLabel setFont: [NSFont menuBarFontOfSize: BarFontSize]];
 | |
| 
 | |
|   cell = [mStatusTextLabel cell];
 | |
|   [cell setPlaceholderString: @""];
 | |
| 
 | |
|   [self addSubview: mStatusTextLabel];
 | |
|   [mStatusTextLabel release];
 | |
| 
 | |
|   // Restore original cell class so that everything else doesn't get broken
 | |
|   [NSTextField setCellClass: oldCellClass];
 | |
| }
 | |
| 
 | |
| //--------------------------------------------------------------------------------------------------
 | |
| 
 | |
| - (void) dealloc
 | |
| {
 | |
|   [mBackground release];
 | |
|   [super dealloc];
 | |
| }
 | |
| 
 | |
| //--------------------------------------------------------------------------------------------------
 | |
| 
 | |
| /**
 | |
|  * Fill the background.
 | |
|  */
 | |
| - (void) drawRect: (NSRect) rect
 | |
| {
 | |
|   // Since the background is seamless, we don't need to take care for the proper offset.
 | |
|   // Simply tile the background over the invalid rectangle.
 | |
|   NSPoint target = {rect.origin.x, 0};
 | |
|   while (target.x < rect.origin.x + rect.size.width)
 | |
|   {
 | |
|     [mBackground drawAtPoint: target fromRect: NSZeroRect operation: NSCompositeCopy fraction: 1];
 | |
|     target.x += mBackground.size.width;
 | |
|   }
 | |
| 
 | |
|   // Draw separator lines between items.
 | |
|   NSRect verticalLineRect;
 | |
|   CGFloat component = 190.0 / 255.0;
 | |
|   NSColor* lineColor = [NSColor colorWithDeviceRed: component green: component blue: component alpha: 1];
 | |
| 
 | |
|   if (mDisplayMask & IBShowZoom)
 | |
|   {
 | |
|     verticalLineRect = [mZoomPopup frame];
 | |
|     verticalLineRect.origin.x += verticalLineRect.size.width + 1.0;
 | |
|     verticalLineRect.size.width = 1.0;
 | |
|     if (NSIntersectsRect(rect, verticalLineRect))
 | |
|     {
 | |
|       [lineColor set];
 | |
|       NSRectFill(verticalLineRect);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   if (mDisplayMask & IBShowCaretPosition)
 | |
|   {
 | |
|     verticalLineRect = [mCaretPositionLabel frame];
 | |
|     verticalLineRect.origin.x += verticalLineRect.size.width + 1.0;
 | |
|     verticalLineRect.size.width = 1.0;
 | |
|     if (NSIntersectsRect(rect, verticalLineRect))
 | |
|     {
 | |
|       [lineColor set];
 | |
|       NSRectFill(verticalLineRect);
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| //--------------------------------------------------------------------------------------------------
 | |
| 
 | |
| - (BOOL) isOpaque
 | |
| {
 | |
|   return YES;
 | |
| }
 | |
| 
 | |
| //--------------------------------------------------------------------------------------------------
 | |
| 
 | |
| /**
 | |
|  * Used to reposition our content depending on the size of the view.
 | |
|  */
 | |
| - (void) setFrame: (NSRect) newFrame
 | |
| {
 | |
|   [super setFrame: newFrame];
 | |
|   [self positionSubViews];
 | |
| }
 | |
| 
 | |
| //--------------------------------------------------------------------------------------------------
 | |
| 
 | |
| - (void) positionSubViews
 | |
| {
 | |
|   NSRect currentBounds = {{0, 0}, {0, [self frame].size.height}};
 | |
|   if (mDisplayMask & IBShowZoom)
 | |
|   {
 | |
|     [mZoomPopup setHidden: NO];
 | |
|     currentBounds.size.width = [mZoomPopup frame].size.width;
 | |
|     [mZoomPopup setFrame: currentBounds];
 | |
|     currentBounds.origin.x += currentBounds.size.width + 1; // Add 1 for the separator.
 | |
|   }
 | |
|   else
 | |
|     [mZoomPopup setHidden: YES];
 | |
| 
 | |
|   if (mDisplayMask & IBShowCaretPosition)
 | |
|   {
 | |
|     [mCaretPositionLabel setHidden: NO];
 | |
|     currentBounds.size.width = [mCaretPositionLabel frame].size.width;
 | |
|     [mCaretPositionLabel setFrame: currentBounds];
 | |
|     currentBounds.origin.x += currentBounds.size.width + 1;
 | |
|   }
 | |
|   else
 | |
|     [mCaretPositionLabel setHidden: YES];
 | |
| 
 | |
|   if (mDisplayMask & IBShowStatusText)
 | |
|   {
 | |
|     // The status text always takes the rest of the available space.
 | |
|     [mStatusTextLabel setHidden: NO];
 | |
|     currentBounds.size.width = [self frame].size.width - currentBounds.origin.x;
 | |
|     [mStatusTextLabel setFrame: currentBounds];
 | |
|   }
 | |
|   else
 | |
|     [mStatusTextLabel setHidden: YES];
 | |
| }
 | |
| 
 | |
| //--------------------------------------------------------------------------------------------------
 | |
| 
 | |
| /**
 | |
|  * Used to switch the visible parts of the info bar.
 | |
|  *
 | |
|  * @param display Bitwise ORed IBDisplay values which determine what to show on the bar.
 | |
|  */
 | |
| - (void) setDisplay: (IBDisplay) display
 | |
| {
 | |
|   if (mDisplayMask != display)
 | |
|   {
 | |
|     mDisplayMask = display;
 | |
|     [self positionSubViews];
 | |
|     [self needsDisplay];
 | |
|   }
 | |
| }
 | |
| 
 | |
| //--------------------------------------------------------------------------------------------------
 | |
| 
 | |
| /**
 | |
|  * Handler for selection changes in the zoom menu.
 | |
|  */
 | |
| - (void) zoomItemAction: (id) sender
 | |
| {
 | |
|   NSNumber* selectedFactorObject = [[sender selectedCell] representedObject];
 | |
| 
 | |
|   if (selectedFactorObject == nil)
 | |
|   {
 | |
|     NSLog(@"Scale popup action: setting arbitrary zoom factors is not yet supported.");
 | |
|     return;
 | |
|   }
 | |
|   else
 | |
|   {
 | |
|     [self setScaleFactor: [selectedFactorObject floatValue] adjustPopup: NO];
 | |
|   }
 | |
| }
 | |
| 
 | |
| //--------------------------------------------------------------------------------------------------
 | |
| 
 | |
| - (void) setScaleFactor: (float) newScaleFactor adjustPopup: (BOOL) flag
 | |
| {
 | |
|   if (mScaleFactor != newScaleFactor)
 | |
|   {
 | |
|     mScaleFactor = newScaleFactor;
 | |
|     if (flag)
 | |
|     {
 | |
|       unsigned count = 0;
 | |
|       unsigned numberOfDefaultItems = sizeof(DefaultScaleMenuFactors) / sizeof(float);
 | |
| 
 | |
|       // We only work with some preset zoom values. If the given value does not correspond
 | |
|       // to one then show no selection.
 | |
|       while (count < numberOfDefaultItems && (fabs(newScaleFactor - DefaultScaleMenuFactors[count]) > 0.07))
 | |
|         count++;
 | |
|       if (count == numberOfDefaultItems)
 | |
|         [mZoomPopup selectItemAtIndex: -1];
 | |
|       else
 | |
|       {
 | |
|         [mZoomPopup selectItemAtIndex: count];
 | |
| 
 | |
|         // Set scale factor to found preset value if it comes close.
 | |
|         mScaleFactor = DefaultScaleMenuFactors[count];
 | |
|       }
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|       // Internally set. Notify owner.
 | |
|       [mCallback notify: IBNZoomChanged message: nil location: NSZeroPoint value: newScaleFactor];
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| //--------------------------------------------------------------------------------------------------
 | |
| 
 | |
| /**
 | |
|  * Called from the notification method to update the caret position display.
 | |
|  */
 | |
| - (void) setCaretPosition: (NSPoint) position
 | |
| {
 | |
|   // Make the position one-based.
 | |
|   int newX = (int) position.x + 1;
 | |
|   int newY = (int) position.y + 1;
 | |
| 
 | |
|   if (mCurrentCaretX != newX || mCurrentCaretY != newY)
 | |
|   {
 | |
|     mCurrentCaretX = newX;
 | |
|     mCurrentCaretY = newY;
 | |
| 
 | |
|     [mCaretPositionLabel setStringValue: [NSString stringWithFormat: @"%d:%d", newX, newY]];
 | |
|   }
 | |
| }
 | |
| 
 | |
| //--------------------------------------------------------------------------------------------------
 | |
| 
 | |
| /**
 | |
|  * Makes the bar resize to the smallest width that can accommodate the currently enabled items.
 | |
|  */
 | |
| - (void) sizeToFit
 | |
| {
 | |
|   NSRect frame = [self frame];
 | |
|   frame.size.width = 0;
 | |
|   if (mDisplayMask & IBShowZoom)
 | |
|     frame.size.width += [mZoomPopup frame].size.width;
 | |
| 
 | |
|   if (mDisplayMask & IBShowCaretPosition)
 | |
|     frame.size.width += [mCaretPositionLabel frame].size.width;
 | |
| 
 | |
|   if (mDisplayMask & IBShowStatusText)
 | |
|     frame.size.width += [mStatusTextLabel frame].size.width;
 | |
| 
 | |
|   [self setFrame: frame];
 | |
| }
 | |
| 
 | |
| @end
 |